KVC
KVC(Key-value coding)键值编码,指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值而不需要调用明确的存取方法。
1、KVC中常见方法
我们随便点击进入setValue:forKey
方法,我们可以发现里面的方法基本上都是基于NSObject
的NSKeyValueCoding
分类写的,所以对于所有继承了NSObject的类型,也就是几乎所有的Objective-C对象都能使用KVC(一些纯Swift类和结构体是不支持KVC的),下面是KVC最为重要的四个方法:
1 | - (nullable id)valueForKey:(NSString *)key; //直接通过Key来取值 |
NSKeyValueCoding
类别中还有其他的一些方法,这些方法在碰到特殊情况或者有特殊需求还是会用到的
1 | //默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索 |
2、KVC的内部实现机制
KVC的setValue:forKey
原理
我们先来一张图片可以直接明了的看清楚实现原理
- 1、按照
setKey
,_setKey
的顺序查找成员方法
,如果找到方法,传递参数,调用方法 - 2、如果没有找到,查看
accessInstanceVariablesDirectly
的返回值(accessInstanceVariablesDirectly
的返回值默认是YES
),- 返回值为YES,按照
_Key,_isKey,Key,isKey
的顺序查找成员变量
,如果找到,直接赋值,如果没有找到,调用setValue:forUndefinedKey:
,抛出异常 - 返回NO,直接调用
setValue:forUndefinedKey:
,抛出异常
- 返回值为YES,按照
KVC的ValueforKey
原理
- 1、按照
getKey,key,isKey,_key
的顺序查找成员方法
,如果找到直接调用取值
- 2、如果没有找到,查看
accessInstanceVariablesDirectly
的返回值 - 返回值为YES,按照
_Key,_isKey,Key,isKey
的顺序查找成员变量
,如果找到,直接取值
,如果没有找到,调用setValue:forUndefinedKey:
,抛出异常 - 返回NO,直接调用
setValue:forUndefinedKey:
,抛出异常
3、KVC的使用
KVC基础使用
假设我们有一个Person
类,里面有一个age
属性,我们给age
赋值和取值
1 | Person *p = [[Person alloc] init]; |
这也是最简单的使用方法了,也是我们平时项目中最常使用的方法了。
KVC中使用keyPath
但是当Person
类里面有一个Student
类,里面有一个height
属性,我们怎么赋值height
属性呢,
1 | @interface Person : NSObject |
我们能否这样写呢
1 | Person *p = [[Person alloc]init]; |
我们运行程序打印结果:
1 | 2020-06-27 15:41:13.085990+0800 KVC[2974:107108] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Person 0x600000d90160> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key stu.height.' |
打印结果是this class is not key value coding-compliant for the key stu.height.
,所以这个方法是不可以的,但是iOS为我们提供了另一个方法KeyPath
:
1 | Person *p = [[Person alloc]init]; |
打印结果:
1 | 2020-06-27 15:43:31.258661+0800 KVC[3012:108720] valueForKey:180 |
keyPath
除了对当前对象的属性进行赋值外,还可以对其更“深层”的对象进行赋值。KVC进行多级访问时,直接类似于属性调用一样用点语法进行访问即可。
KVC之集合属性
如果我们想要修改集合类型,我们该怎么办呢,不要着急,系统还是很友好的给我们提供了一些方法
1 | - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key; |
简单使用
1 | Person *p = [[Person alloc] init]; |
关于mutableArrayValueForKey:
的适用场景,网上一般说是在KVO
中,因为KVO的本质是系统监测到某个属性的内存地址或常量改变
时会添加上- (void)willChangeValueForKey:(NSString *)key
和- (void)didChangeValueForKey:(NSString *)key
方法来发送通知,但是如果直接改数组的话,内存地址并没有改变。
1 | - (void)viewDidLoad { |
我们分别用 [_p.list addObject:@(arc4random()%255)];
和[[self.p mutableArrayValueForKey:@"list"] addObject:@(arc4random()%255)];
两个方法修改list
内容,我们打印可知 [_p.list addObject:@(arc4random()%255)];
方法并没有改变list
的内存地址,而使用[[self.p mutableArrayValueForKey:@"list"] addObject:@(arc4random()%255)];
, list
的内存地址改变了。
KVC之字典属性
KVC里面还有两个关于NSDictionary的方法
1 | - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys; |
dictionaryWithValuesForKeys:
是指输入一组key,返回这组key对应的属性,再组成一个字典setValuesForKeysWithDictionary
是用来修改dic中对应key的属性
这个属性最常用到的地方就是字典转模型
例如我们有一个Student
类,
1 | @interface Student : NSObject |
我们正常是怎么赋值呢
1 | Student *stu = [[Student alloc]init]; |
如果里面有100个属性呢,我们就需要写100遍。如果使用setValuesForKeysWithDictionary
方法呢
1 | Student *stu = [[Student alloc]init]; |
这样是不是简单了好多。
4、KVC异常处理
当根据KVC搜索规则,没有搜索到对应的key或者keyPath,则会调用对应的异常方法。异常方法的默认实现,在异常发生时会抛出一个NSUndefinedKeyException
的异常,并且应用程序Crash
。我们可以重写下面两个方法,根据业务需求合理的处理KVC导致的异常。
1 | - (nullable id)valueForUndefinedKey:(NSString *)key; |
其中重写这两个方法,在key
值不存在的时候,会走下面方法,而不会异常抛出
1 | - (nullable id)valueForUndefinedKey:(NSString *)key; |
重写这个方法,当value值为nil的时候,会走下面方法,而不会异常抛出
1 | - (void)setNilValueForKey:(NSString *)key; |
5、KVC的正确性验证
在调用KVC时可以先进行验证,验证通过下面两个方法进行,支持key和keyPath两种方式。验证方法默认实现返回YES,可以通过重写对应的方法修改验证逻辑。
验证方法需要我们手动调用,并不会在进行KVC的过程中自动调用
1 | - (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError; |
在validateValue方法的内部实现中,如果传入的value或key有问题,可以通过返回NO来表示错误,并设置NSError对象。