iOS键值编码KVC

2018-06-19  本文已影响0人  帅狗黑皮668

关于KVC的实现原理,网上有好多相关的文章,大同小异,感觉还是形成自己的语言写下来比较好,特写此文。

KVC是什么

kvc全称是(Key-value coding)键值编码,定义在NSKeyValueCoding.h文件中。平常我们通过setter、getter方法来设置和修改对象的属性,实际上KVC属于一种更加灵活的操作方式,这种方式我们可以通过Key名直接访问对象的属性,或者给对象的属性赋值。

协议方法

比较常用的四个方法

- (nullable id)valueForKey:(NSString *)key;                          //直接通过Key来取值
- (void)setValue:(nullable id)value forKey:(NSString *)key;          //通过Key来设值
- (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通过KeyPath来设值

NSKeyValueCoding中提供了KVC通用的访问方法,分别是getter方法valueForKey:和setter方法setValue:forKey:,以及其衍生的keyPath方法,这两个方法各个类通用的。并且由KVC提供默认的实现,我们也可以自己重写对应的方法来改变实现。

使用keyPath方法

forKeyPath中可以利用.运算符, 就可以一层一层往下查找对象的属性
当然 在一般的修改一个对象的属性的时候,forKey和forKeyPath,没什么区别。如:
Person *p = [Person new];
[p setValue:@"jack" forKey:@"name"];
[p setValue:@"jack" forKeyPath:@"name"];
这两个是没有区别的。
但是若是层次结构深一点, 比如说Person里面有个Dog,Dog里有个Bone,如果给Bone里面的type赋值的时候,则可以[p setValue:@"猪骨" forKeyPath:@"dog.bone.type"]

调用顺序

基础Setter搜索模式

苹果官方文档中说的是:链接大致翻译为:

  1. 按顺序查找名为set<Key>:_set<Key>命名的setter。 如果找到,则用输入值调用它并完成。

  2. 如果没有找到简单的setter,并且类方法accessInstanceVariablesDirectly返回YES,则按照该顺序寻找名称类似于_<key>_is<Key><key><Key>的实例变量。 如果找到,直接用输入值设置变量并结束

  3. 如果找不到Setter或实例变量,调用setValue:forUndefinedKey:.并抛出异常。

基础Getter搜索模式

苹果官方文档说的是:链接大致翻译为:

  1. 按照该顺序搜索名为get<Key><key>is<Key>_<key>的第一个Getter实例。 如果找到,调用它并继续执行第5步并显示结果。 否则,请继续下一步。

  2. 如果没有找到简单的Getter方法,则在实例中搜索名称与模式countOf<Key>objectIn<Key>AtIndex:(与NSArray类定义的原始方法相对应)和<key>AtIndexes:(对应于NSArray方法objectsAtIndexes:)。
    如果找到其中的第一个和其他两个中的至少一个,则创建一个集合代理对象,该对象响应所有NSArray方法并返回该对象。否则,请继续执行步骤3。
    代理对象随后将它接收到的任何NSArray消息转换为countOf<Key>objectIn<Key>AtIndex:,和<Key> AtIndexes:消息到创建它的键值编码兼容对象的某种组合。如果原始对象也实现了一个名为get<Key>:range:的可选方法,则代理对象也会在适当的时候使用它。

  3. 如果上面的方法没有找到,那么会同时查找countOf<Key>enumeratorOf<Key>,memberOf<Key>格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所的方法的代理集合,和上面一样,给这个代理集合发NSSet的消息,就会以countOf<Key>enumeratorOf<Key>,memberOf<Key>组合的形式调用

  4. 如果没有发现简单getter方法,或集合存取方法组,以及接收类方法accessInstanceVariablesDirectly是返回YES的。搜索一个名为_<key>_is<Key><key>is<Key>的实例,根据他们的顺序。如果发现对应的实例,则立刻获得实例可用的值并跳转到第5步,否则,跳转到第6步。

  5. 如果取回的是一个对象指针,则直接返回这个结果。
    如果取回的是一个基础数据类型,但是这个基础数据类型是被NSNumber支持的,则存储为NSNumber并返回。
    如果取回的是一个不支持NSNumber的基础数据类型,则通过NSValue进行存储并返回。

  6. 如果所有情况都失败,则调用valueForUndefinedKey:方法并抛出异常,这是默认行为。

异常处理

处理不存在的key

在使用KVC时,如果没有找到对应的key或者keyPath,则会抛出NSUndefinedKeyException的异常,并导致程序crash,此时我们可以考虑重写setValue: forUndefinedKey:方法与valueForUndefinedKey:方法来避免这个问题。

处理nil值

如果属性的类型是基础类型(int,float,double等),如果将属性设置成nil,程序就会抛出NSInvalidArgumentException的异常,并导致Crash,此时我们可以重写setNilValueForKey:方法来处理这个问题。

属性验证

KVC提供了对key或者keyPath进行验证的方法

- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;

当开发者需要验证能不能用KVC设定某个值时,可以调用validateValue: forKey:这个方法来验证,如果这个类的开发者实现了-(BOOL)validate<Key>:error:这个方法,那么KVC就会直接调用这个方法来返回,如果没有,就直接返回YES,注意!注意!注意(重要的事情说三遍)KVC在设值时不会主动去做验证,需要开发者手动去验证。

Example

@implementation Person
- (BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError{
    NSString *name = *ioValue;
    if ([name isEqualToString:@"Lucy"]) {
        return YES;
    }
    return NO;
}


Person *add = [Person new];
    NSError *error;
    NSString *name = @"Jack";
    if (![add validateValue:&name forKey:@"name" error:&error]) {
        NSLog(@"error");
    }

KVC字典转模型

将后台json数据中的字典转模型,比较好的框架有SBJSON、JSONKit、MJExtension、YYModel等。但是一些简单的数据,我们可以自己用代码来实现转换。一般我都会写NSObjec的类扩展。实现代码如下:

- (id)initWithDict:(NSDictionary *)dict{
    if (self = [self init]) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}

+ (id)modelWithDict:(NSDictionary *)dict{
    return [[self alloc] initWithDict:dict];
}

在使用的时候直接调用modelWithDict:就行了,但是有个弊端就是如果找不到对应的key值,就会报错导致Crash,此时只要在对应的Model类重写setValue:forUndefinedKey:方法即可。

上一篇 下一篇

猜你喜欢

热点阅读