Core Data 数据验证

本文前半段通过整理苹果官网的技术文档——《Core Data Validation》得出。此外提供了一段进行Core Data数据验证的代码,点击这里下载。文章后半段是对这份代码的简单解释。

根据苹果官网的描述,Core Data的数据验证分为两种类型。分别被称为属性级(property-level)和内部级(inter-property)。属性级验证用于验证单个数据是否合法,内部级验证用于验证若干个数据的组合值是否合法。

Cocoa框架提供了一些最基本的数据验证,比如一个数值型属性的最大最小值,字符串的长度范围,一个字符串必须匹配的模式等,还可以对关系(Relationship)进行一些限定。因此,对于很多数据验证,不必写代码即可完成。

如果想自定义数据的验证,我们需要使用在NSKeyValueCoding协议中被定义的标准方法。很重要的一点在于,如何验证是由数据自身的特性决定,但何时验证是由用户决定的。这也就是说,对于一个内存中的对象来说,临时的不满足验证要求是没问题的,因为验证方法仅在上下文(Context)被保存或者用户手动调用时才会被执行。当然,用户也可以在数据每次发生改变的时候执行验证,这样可以立即发现错误,以免最后得到一长串的错误。此外,这样使得上下文(context)更像是一个“便笺本”,我们在本子上随意修改数据,直至最后保存或者丢弃这些数据。

属性级验证(Property-Level Validation)

NSKeyValueCoding协议指定了一个validateValue:forKey:error:方法可以给验证方法提供一些最基本的支持。但如果我们想要实现一些自定义的数据验证逻辑,我们不能重写validateValue:forKey:error:方法,而是应该实现形如 validate< Key >:error:的方法。

但我们一般不去直接调用这些方法,而是调用validateValue:forKey:error:方法,并指定合适的Key,这保证了我们对所有属性的验证都被执行。这是因为,在通常情况下,validateValue:forKey:error: 方法会在接受者的类中查找并执行所有格式满足validate< Key >:error:的方法。如果没有方法被找到,validateValue:forKey:error: 方法会直接返回YES作为验证结果。

一个典型的验证方法大概是这样:

-(BOOL)validateAge:(id *)ioValue error:(NSError **)outError {
    BOOL validate = YES;
    //Do some validations
    return validate;
}

我们检查ioValue属性,如果它不满足我们的验证要求,就返回NO. 如果此时outError参数不为空, 我们还可以创建一个NSError类的对象来描述这个错误。

内部级验证(Inter-Property validation)

很有可能出现的一种情况是,每一个单独的属性都是符合验证要求的但是它们组合在一起的值是不符合要求的。比如,12岁是一个人的合理年龄,YES也是hasDrivingLicense这一属性的合理值,但是一个12岁的人具有驾照(在大部分国家)是不合理的。

NSManagedObject通过形如validateFor…的方法(比如validateForUpdate:方法),在更新、插入和删除的时候,提供额外的验证机会。如果我们要实现自定义的内部级验证方法,我们要先调用它的父类方法来验证每一个每一个单独的属性都先被验证是满足验证要求的。如果存在一个或多个不符合验证要求的属性,我们有两种选择:

  1. 直接返回NO和由相应的验证方法产生的错误信息
  2. 继续验证这些属性的组合值是否合法。

如果我们选择继续验证,就必须保证那些不符合验证要求的属性不会带来严重的错误。比如1➗0的情况。而且在继续验证的过程中,如果我们还发现了其他的不满足,需要把前后的错误信息拼接起来。

举例说明

我们先来看一看属性级验证(Property-Level Validation)是如何实现的。

//Team.m
- (BOOL)validateName:(id *)ioValue error:(NSError **)outError
{
    NSString *playerName = *ioValue;
    playerName = [playerName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    if (!playerName || [playerName length] == 0) {
        if (outError) {
            NSString *errorStr = @"Team's name should not be empty.";
            NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorStr };
            NSError *error = [[NSError alloc] initWithDomain:TEAM_ERROR_DOMAIN
                                                        code:TEAM_INVALID_NAME_CODE
                                                    userInfo:userInfoDict];
            *outError = error;
        }
        return NO;
    }

    return YES;
}

需要注意的一点是,这个方法名不能被随意修改,此前也说过,validateValue:forKey:error:方法会根据固定的格式来查找需要被调用的方法。我们在实现这个方法的时候,Xcode不会给出代码提示,我们需要遵循“ - (BOOL)validate属性名:(id )ioValue error:(NSError *)outError”的规则来实现。

另外,这里传入的参数是id *类型的,这也就意味着我们甚至可以修改被传入的参数。但是苹果强烈反对这么做,因为可能会引起严重的内存管理问题。

当验证方法被实现后,如果尝试执行下面这样的代码,将会得到报错。

NSManagedObject *teamObject = [NSEntityDescription insertNewObjectForEntityForName:@"Team" inManagedObjectContext:delegate.managedObjectContext];
[teamObject setValue:@" " forKey:@"name"];
[teamObject setValue:@"Philadelphia" forKey:@"city"];
[delegate saveContext];

错误信息:

2015-06-23 05:01:22.849 MultiThreadCoreData[1042:306970] Unresolved error Error Domain=TEAM_ERROR_DOMAIN Code=1 "Team's name should not be empty." UserInfo=0x7f91d2f7e1c0 {NSLocalizedDescription=Team's name should not be empty.}, {
    NSLocalizedDescription = "Team's name should not be empty.";
}
(lldb) 

这样的错误直接导致程序crash,我们通常只是希望提前验证一下而已。所以可以这么实现:

    NSError *error = [[NSError alloc]init];
    NSString *name = @" ";
    [teamObject validateValue:&name forKey:@"name" error:&error];
    if (error) {
        NSLog(@"%@",[error localizedDescription]);
    }
    else{
        [delegate saveContext];
    }

于是我们只会得到一行警告,同时可以保证saveContext方法不被调用。

2015-06-23 05:04:53.518 MultiThreadCoreData[1078:318537] Team's name should not be empty.

至于内部级验证,主要涉及error的叠加。但是其实现方法和属性级验证非常类似,这里就不给出具体代码,如果有兴趣,可以查阅这篇文章

你可能感兴趣的:(数据,验证,属性,Data,core)