[英]Core Data validation: from Objective-C to Swift
I'm building a dummy iOS project in order to understand how to implement validation in Core Data with Swift. 我正在构建一个虚拟iOS项目,以了解如何使用Swift在Core Data中实现验证。 The Core Data model of the project has one entity called
Person
that contains two attributes: firstName
and lastName
. 项目的核心数据模型有一个名为
Person
实体,它包含两个属性: firstName
和lastName
。 The project is based on Swift but, in order to start it, I'm using Objective-C to define the NSManagedObject
subclass: 该项目基于Swift,但为了启动它,我使用Objective-C来定义
NSManagedObject
子类:
Person.h Person.h
@interface Person : NSManagedObject
@property (nonatomic, retain) NSString *firstName;
@property (nonatomic, retain) NSString *lastName;
@end
Person.m Person.m
@implementation Person
@dynamic firstName;
@dynamic lastName;
-(BOOL)validateFirstName:(id *)ioValue error:(NSError **)outError {
if (*ioValue == nil || [*ioValue isEqualToString: @""]) {
if (outError != NULL) {
NSString *errorStr = NSLocalizedStringFromTable(@"First name can't be empty", @"Person", @"validation: first name error");
NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorStr };
NSError *error = [[NSError alloc] initWithDomain:@"Domain" code: 101 userInfo: userInfoDict];
*outError = error;
}
return NO;
}
return YES;
}
@end
Person-Bridging-Header.h 人桥接,Header.h
#import "Person.h"
In the Core Data Model Editor, I've set the entity class inside the Data Model Inspector as indicated: 在核心数据模型编辑器中,我按照指示在数据模型检查器中设置了实体类:
class: Person
The first time I launch the project, I create an instance of Person
in the AppDelegate
application:didFinishLaunchingWithOptions:
method with the following code: 第一次启动项目时,我在
AppDelegate
application:didFinishLaunchingWithOptions:
创建了一个Person
实例application:didFinishLaunchingWithOptions:
方法,其代码如下:
if !NSUserDefaults.standardUserDefaults().boolForKey("isNotInitialLoad") {
let person = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: managedObjectContext!) as Person
person.firstName = "John"
person.lastName = "Doe"
var error: NSError?
if !managedObjectContext!.save(&error) {
println("Unresolved error \(error), \(error!.userInfo)")
abort()
}
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "isNotInitialLoad")
NSUserDefaults.standardUserDefaults().synchronize()
}
The project has one UIViewController
with the following code: 该项目有一个
UIViewController
,代码如下:
class ViewController: UIViewController {
var managedObjectContext: NSManagedObjectContext!
var person: Person!
override func viewDidLoad() {
super.viewDidLoad()
//Fetch the Person object
var error: NSError?
let fetchRequest = NSFetchRequest(entityName: "Person")
let array = managedObjectContext.executeFetchRequest(fetchRequest, error:&error)
if array == nil {
println("Unresolved error \(error), \(error!.userInfo)")
abort()
}
person = array![0] as Person
}
@IBAction func changeFirstName(sender: AnyObject) {
//Generate a random firstName
let array = ["John", "Jimmy", "James", "Johnny", ""]
person.firstName = array[Int(arc4random_uniform(UInt32(5)))]
var error: NSError?
if !managedObjectContext.save(&error) {
println("Unresolved error \(error), \(error!.userInfo)")
return
}
//If success, display the new person's name
println("\(person.firstName)" + " " + "\(person.lastName)")
}
}
changeFirstName:
is linked to a UIButton
. changeFirstName:
链接到UIButton
。 Therefore, whenever I click on this button, a new String
is randomly generated and assigned to person.firstName
. 因此,每当我单击此按钮时,将随机生成一个新
String
并将其分配给person.firstName
。 If this new String
is empty, validateFirstName:error:
generates a NSError
and the save operation fails. 如果此新
String
为空,则validateFirstName:error:
生成NSError
并且保存操作失败。
This works great but, in order to have a pure Swift project, I've decided to delete Person.h
, Person.m
and Person-Bridging-Header.h
and to replace them with a single Swift file: 这很好但是,为了有一个纯粹的Swift项目,我决定删除
Person.h
, Person.m
和Person-Bridging-Header.h
并用一个Swift文件替换它们:
class Person: NSManagedObject {
@NSManaged var firstName: String
@NSManaged var lastName: String
func validateFirstName(ioValue: AnyObject, error: NSErrorPointer) -> Bool {
if ioValue as? String == "" {
if error != nil {
let myBundle = NSBundle(forClass: self.dynamicType)
let errorString = myBundle.localizedStringForKey("First name can't be empty", value: "validation: first name error", table: "Person")
let userInfo = NSMutableDictionary()
userInfo[NSLocalizedFailureReasonErrorKey] = errorString
userInfo[NSValidationObjectErrorKey] = self
var validationError = NSError(domain: "Domain", code: NSManagedObjectValidationError, userInfo: userInfo)
error.memory = validationError
}
return false
}
return true
}
}
In the Core Data Model Editor, I've also changed the entity class inside the Data Model Inspector as indicated: 在核心数据模型编辑器中,我还更改了数据模型检查器中的实体类,如下所示:
class: Person.Person //<Project name>.Person
The problem now is that the project crashes whenever I call changeFirstName:
. 现在的问题是每当我调用
changeFirstName:
时项目都会崩溃changeFirstName:
。 The weirdest thing is that if I put a breakpoint inside validateFirstName:
, I can see that this method is never called. 最奇怪的是,如果我在
validateFirstName:
放置一个断点,我可以看到永远不会调用此方法。
What am I doing wrong? 我究竟做错了什么?
I am a little bit guessing here, but the (id *)ioValue
parameter is mapped to Swift as 我在这里有点猜测,但是
(id *)ioValue
参数映射到Swift as
ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>
therefore the Swift variant should probably look like 因此,Swift变体应该看起来像
func validateFirstName(ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>, error: NSErrorPointer) -> Bool {
if let firstName = ioValue.memory as? String {
if firstName == "" {
// firstName is empty string
// ...
}
} else {
// firstName is nil (or not a String)
// ...
}
return true
}
Update for Swift 2: Swift 2更新:
func validateFirstName(ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws {
guard let firstName = ioValue.memory as? String where firstName != "" else {
// firstName is nil, empty, or not a String
let errorString = "First name can't be empty"
let userDict = [ NSLocalizedDescriptionKey: errorString ]
throw NSError(domain: "domain", code: NSManagedObjectValidationError, userInfo: userDict)
}
// firstName is a non-empty string
}
As @SantaClaus correctly noticed, the validation function must now throw an error if the validation fails. 正如@SantaClaus正确注意到的那样,验证函数现在必须在验证失败时抛出错误。
Apple's Core Data Programming Guide is now updated for Swift 3. Here's the example code from the Managing Object Life Cycle > Object Validation page ( memory
has been renamed to pointee
): Apple的核心数据编程指南现在已针对Swift 3进行了更新。以下是管理对象生命周期>对象验证页面中的示例代码(
memory
已重命名为pointee
):
func validateAge(value: AutoreleasingUnsafeMutablePointer<AnyObject?>!) throws {
if value == nil {
return
}
let valueNumber = value!.pointee as! NSNumber
if valueNumber.floatValue > 0.0 {
return
}
let errorStr = NSLocalizedString("Age must be greater than zero", tableName: "Employee", comment: "validation: zero age error")
let userInfoDict = [NSLocalizedDescriptionKey: errorStr]
let error = NSError(domain: "EMPLOYEE_ERROR_DOMAIN", code: 1123, userInfo: userInfoDict)
throw error
}
EDIT: The example is not quite right. 编辑:这个例子不太正确。 To get it to work, I've changed
AutoreleasingUnsafeMutablePointer<AnyObject?>
to an unwrapped optional and value?.pointee
to value.pointee
. 为了使它工作,我已经将
AutoreleasingUnsafeMutablePointer<AnyObject?>
更改为一个未包装的可选value?.pointee
和value?.pointee
到value.pointee
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.