Object Validation
cocoa提供了一个基本的model值验证的机制。但是它要求你必须为所有想用的地方写代码。core data,另一方面允许你把验证逻辑放到managed 对象model中并且书写验证逻辑。
How Validation Works in Core Data
如何验证是一个model方案。当被验证的是一个用户接口或者controller级别的方案时。例如,一个text field绑定的值可能设置了validates immediately选项。
一个内存中的对象可能短期内变为不一致的。只有在save操作或者request的时候,验证约束才会提供。有时候,需要在数据有变化时就验证并且即刻报告错误。如果managed 对象被要求总是有效的状态,会强制用户的特定流程。
Implementing Custom Property-Level Validation
NSKeyValueCoding
协议指定了validateValue:forKey:error:
方法来提供对于验证方法的一般支持。
In the method implementation, you check the proposed new value, and if it does not fit your constraints, you returnNO
. If the error parameter is notnull
, you also create an NSError
object that describes the problem, as illustrated in the following example. The example validates that the age value is greater than zero. If it is not, an error is returned.
- (BOOL)validateAge:(id*)ioValueerror:(NSError**)outError
{
if (*ioValue== nil) {
return YES;
}
if ([*ioValuefloatValue] <=0.0) {
if (outError== NULL) {
return NO;
}
NSString *errorStr= NSLocalizedStringFromTable(@"Age must be greater than zero",@"Employee", @"validation: zero age error");
NSDictionary *userInfoDict = @{NSLocalizedDescriptionKey:errorStr};
NSError *error= [[NSError alloc] initWithDomain:EMPLOYEE_ERROR_DOMAINcode:PERSON_INVALID_AGE_CODE userInfo:userInfoDict];
*outError= error;
return NO;
} else{
return YES;
}
}
func validateAge(value:AutoreleasingUnsafeMutablePointer<AnyObject?>)throws {
if value ==nil {
return
}
let valueNumber =value.memory 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
}
Implementing Custom Interproperty Validation
一个对象的所有独立的属性可能是valid但是不同属性值的组合可能就是invalid的。例如,应用程序存储了人的age和是否有驾照。对于一个person对象,12可能是一个有效的age属性值,yes是一个有效的hasDrivingLicense
值。但是这两个条件一起可能就是invalid的值。
NSManagedObject
提供了额外的机会来验证-更新,插入,删除-通过方法validateFor…
例如validateForUpdate:
.如果你实现自定义的验证方法,你应当首先调用super class的实现来确保单独的属性验证被触发了。如果superclass的实现失败,那么你就可以做如下的操作:
- 返回no和superclass实现的error
- 继续执行验证,查找矛盾的合并值。
如果你继续执行验证,确保在你逻辑中的任何值不是本身invalid导致的错误。例如,假设你用了一个值为0的属性作为除数,但是值却是要求大于0的。如果你进一步发现了验证错误,你必须把她们同现存的错误合并起来并且返回一个multiple errors error”作为描述。
下面的例子展示了一个验证方法的实现。person实体有两个属性,birthday和hasdrivinglicense。约束是person小于16岁的不能有驾照。在validateForInsert:
和validateForUpdate:
,中这个约束都做了检查。因此验证逻辑就单独放在了一个方法中。
Listing 14-1Interproperty validation for a Person entity
- (BOOL)validateForInsert:(NSError**)error
{
BOOL propertiesValid= [super validateForInsert:error];
// could stop here if invalid
BOOL consistencyValid= [self validateConsistency:error];
return (propertiesValid&& consistencyValid);
}
- (BOOL)validateForUpdate:(NSError**)error
{
BOOL propertiesValid= [super validateForUpdate:error];
// could stop here if invalid
BOOL consistencyValid= [self validateConsistency:error];
return (propertiesValid&& consistencyValid);
}
- (BOOL)validateConsistency:(NSError**)error
{
static NSCalendar*gregorianCalendar;
NSDate *myBirthday= [self birthday];
if (myBirthday== nil) {
return YES;
}
if ([[selfhasDrivingLicense] boolValue] == NO) {
return YES;
}
if (gregorianCalendar== nil) {
gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
}
NSDateComponents *components = [gregorianCalendar components:NSCalendarUnitYearfromDate:myBirthday toDate:[NSDate date] options:0];
NSInteger years= [componentsyear];
if (years>= 16) {
return YES;
}
if (error== NULL) {
//don't create an error if none was requested
return NO;
}
NSBundle *myBundle= [NSBundle bundleForClass:[self class]];
NSString *drivingAgeErrorString= [myBundle localizedStringForKey:@"TooYoungToDriveError" value:@"Person is too young to have a driving license."table:@"PersonErrorStrings"];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfosetObject:drivingAgeErrorString forKey:NSLocalizedFailureReasonErrorKey];
[userInfosetObject:self forKey:NSValidationObjectErrorKey];
NSError *drivingAgeError= [NSError errorWithDomain:EMPLOYEE_ERROR_DOMAIN code:NSManagedObjectValidationError userInfo:userInfo];
if (*error== nil) { // if there was no previous error, return the new error
*error= drivingAgeError;
} else{ // if there was a previous error, combine it with the existing one
*error= [self errorFromOriginalError:*errorerror:drivingAgeError];
}
return NO;
}
override funcvalidateForInsert() throws {
try super.validateForInsert()
try validateConsistency()
}
override funcvalidateForUpdate() throws {
try super.validateForUpdate()
try validateConsistency()
}
func validateConsistency()throws {
guard letmyBirthday = birthday else {
return
}
if !hasDrivingLicense {
return
}
let gregorianCalendar =NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let components =gregorianCalendar.components(.Year,fromDate: myBirthday, toDate: NSDate(), options:.WrapComponents)
if components.year >=16 {
return
}
let errString ="Person is too young to have a driving license."
let userInfo = [NSLocalizedFailureReasonErrorKey:errString, NSValidationObjectErrorKey:self]
let error =NSError(domain: "EMPLOYEE_ERROR_DOMAIN",code: 1123, userInfo:userInfo)
throw error
}
Combining Validation Errors
如果在一个操作中出现了多个验证失败,就可以通过NSValidationMultipleErrorsError
来创建和返回一个nserror对象。通过NSDetailedErrorsKey
将这些错误添加到了数组中。
注意:合并错误目前swift不支持
Listing 14-2A method for combining two errors into a single multiple errors error
- (NSError*)errorFromOriginalError:(NSError*)originalErrorerror:(NSError*)secondError
{
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
NSMutableArray *errors = [NSMutableArrayarrayWithObject:secondError];
if ([originalErrorcode] == NSValidationMultipleErrorsError) {
[userInfoaddEntriesFromDictionary:[originalErroruserInfo]];
[errorsaddObjectsFromArray:[userInfoobjectForKey:NSDetailedErrorsKey]];
} else{
[errorsaddObject:originalError];
}
[userInfosetObject:errors forKey:NSDetailedErrorsKey];
return [NSErrorerrorWithDomain:NSCocoaErrorDomaincode:NSValidationMultipleErrorsErroruserInfo:userInfo];
}