KVC非对象属性的表示以及属性的验证

非对象 value的表示

NSObject提供的KVC协议方法的默认实现对对象和非对象属性都是有效的。默认的实现将自动转换对象参数或者返回值,以及非对象属性。这将允许基于keygetterssetters的特征保持一致,即使存储的属性是基本数据变量或结构体。

因为Swift中所有的属性都是对象,这部分仅适用OC属性。

当调用协议的getters方法时,比如valueForKey:,默认实现将根据KVC方法的搜索模式中描述的规则为指定的key提供特定访问方法或者实例变量。如果返回的值不是对象,则getters会将值初始化为NSNumber对象(对于基本数据类型)或者NSValue(对于结构体)返回。

同样地,默认情况下,setter方法比如setValue:forKey:根据特定的key来确定属性的访问方法或者实例变量所需的数据类型。如果数据类型不是对象,setters首先向传入值(value)发送适当的Value消息以获取底层数据,然后将其存储。

注意:

当你调用kvc协议中的一个方法为非对象属性设置一个 nil值时,它会向收到setter调用的对象发送一个setNilValueForKey:消息。该方法的默认实现会抛出NSInvalidArgumentException异常,但子类可以覆盖该方法。

基本数据类型的Wrap 和 Unwrap

下表列出了默认KVC实现中使用NSNumber实例包装的基本数据类型。

Data type Creation method Accessor method
BOOL numberWithBool: boolValue (in iOS)
charValue (in macOS)*
char numberWithChar: charValue
double numberWithDouble: doubleValue
float numberWithFloat: floatValue
int numberWithInt: intValue
long numberWithLong: longValue
long long numberWithLongLong: longLongValue
short numberWithShort: shortValue
unsigned char numberWithUnsignedChar: unsignedChar
unsigned int numberWithUnsignedInt: unsignedInt
unsigned long numberWithUnsignedLong: unsignedLong
unsigned long long numberWithUnsignedLongLong: unsignedLongLong
unsigned short numberWithUnsignedShort: unsignedShort

结构体的Wrap 和 Unwrap

Data type Creation method Accessor method
CGPoint valueWithCGPoint: CGPointValue
NSRange valueWithRange: rangeValue
CGRect valueWithCGRect: CGRectValue
CGSize valueWithCGSize: CGSizeValue

当然,还有其他的结构体,比如UIEdgeInsets,CGVector以及我们自定义的结构体等等,这里就不一一列举了。

验证属性

KVC协议定义了支持属性验证的方法,就像使用基于key的访问方法来读写属性一样,我们也可以通过key(或keypath)来验证属性。 当调用validateValue:forKey:error:(或validateValue:forKeyPath:error:)方法,协议的默认实现是查找接收到验证消息的对象(或者keypath中结尾的对象)的名称匹配validate:error:的方法。如果对象没有该方法,默认情况下验证成功,当指定的属性的验证方法存在时,默认实现将返回该方法的结果。

注意:

通常仅在Objective-C中使用此处所述的验证。 在Swift中,属性验证通过依赖于编译器和强类型检查的支持来处理,同时使用内置的willSetdidSet属性观察器来测试任何运行时API.

由于指定属性的验证方法通过引用valueerror参数,验证有三种可能的结果:

  1. 验证方法认为value对象有效并返回YES,(不会更改valueerror)
  2. 验证方法认为value对象无效,但并不改变它。这种情况,方法会返回NO并且将error参数(如果调用者提供了的话)设置为指示失败原因的NSError对象
  3. 验证方法认为value对象无效,并且新创建一个有效的作为替代。 这种情况下,该方法在error对象不变的情况下返回YES。在返回之前,该方法修改value的引用以指向新的value对象。当它进行修改时,该方法总是创建一个新的对象,而不是修改旧对象,即使value对象是可变的。

validateValue:forKey:error:

- (BOOL)validateValue:(inout id  _Nullable *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable *)outError;
  • ioValue: 指向由inKey标识的属性的新值的指针。 此方法可能会修改或替换该值以使其有效。
  • inKey: receiver的一个属性名称。必须是指定的Attribute或者一个to-one关系。
  • outError: 如果需要验证并且ioValue未转换为有效值,则在返回时包含描述ioValue不是有效值的原因的NSError对象。
  • 返回值: 如果ioValue指向的值对于由inKey标识的属性有效,或者该方法能够修改ioValue中的值使其有效,则返回YES; 否则为NO。
validateValue:forKey:error:方法的默认实现

该方法的默认实现是查找receiver类中方法名匹配-validate:error:的方法,如果你为属性定义了这样的方法,当验证相应的属性时,validateValue:forKey:error:的默认实现会调用-validate:error:方法。

如果没有找到-validate:error:方法,validateValue:forKey:error: 返回 YES.

下面示例展示了如何为name字符串调用验证的 示例:

  Person* person = [[Person alloc] init];
  NSError* error;
  NSString* name = @"John";
  BOOL validation = [person validateValue:&name forKey:@"name" error:&error];
  if (validation) {
      [person setValue:name forKey:@"name"];
  }
  else {
      NSLog(@"error: %@",error);
  }

Person类实现了name的验证方法(这里设置了name必须是长度大于5的字符串)

- (BOOL)validateName:(inout id  _Nullable __autoreleasing *)ioValue error:(out NSError * _Nullable __autoreleasing *)outError
{
    NSString *reason =  nil;
    if ([*ioValue isKindOfClass:[NSString class]]) {
        NSString *name = (NSString *)*ioValue;
        if (name.length > 5) {
            return YES;
        }
        else {
            reason = @"取名字必需大于5个字符";
        }
    }
    else {
        reason = @"name 必须是字符串";
    }
  if (outError != NULL) {
    NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey:reason};
    
    *outError = [NSError errorWithDomain:NSLocalizedDescriptionKey code:10086 userInfo:userInfo];
  }
    return NO;
}
validateValue:forKeyPath:error:
- (BOOL)validateValue:(inout id  _Nullable *)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError * _Nullable *)outError;

此方法的默认实现使用valueForKey获取每个关系的目标对象,并返回为该属性调用validateValue:forKey:error:方法的结果。

自动验证

通常,KVC协议及其默认实现都不定义任何自动执行验证的机制。相反,我们可以在app适当的时候使用验证方法。

在某些情况下,某些其他Cocoa技术会自动执行验证。 例如,Core Data在保存管理对象上下文时自动执行验证(感兴趣的可以查看Core Data Programming Guide)。

参考:

  • 苹果开发文档

你可能感兴趣的:(KVC非对象属性的表示以及属性的验证)