本文首发于个人博客
前言
- KVC是Key Value Coding的简称。它是一种可以通过字符串的名字(key)来访问类属性的机制。而不是通过调用Setter、Getter方法访问。KVC的方法定义在Foundation/NSKeyValueCoding中。
- KVC和KVO都属于键值编程而且底层实现机制都是isa-swizzing。
常见的API有
- -(void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- -(void)setValue:(id)value forKey:(NSString *)key;
- -(id)valueForKeyPath:(NSString *)keyPath;
- -(id)valueForKey:(NSString *)key;
KVC基本使用
- 定义一个YZPerson类,有个 name 属性
1 | @interface YZPerson : NSObject |
- ViewController 控制器中,如下使用
1 |
|
- 结果为
1 | KVCDemo[25838:347883] jack |
赋值 setValue:forKey:的原理
- 按照 setKey:、_setKey: 的顺序查找方法
- 如果找到了方法,就传递参数,调用方法
- 如果没有找到,查看 accessInstanceVariablesDirectly 方法的返回值
- 如果accessInstanceVariablesDirectly 返回值为 NO 调用
setValue:forUndefinedKey: 并抛出异常 NSUnknownKeyException
5. 如果accessInstanceVariablesDirectly 返回值为 YES 按照_key、_isKey、key、isKey的顺序查找成员变量
6. 如果找到了成员变量,就直接赋值。
7. 如果 _key、_isKey、key、isKey的顺序没有查找到成员变量就调用setValue:forUndefinedKey: 并抛出异常 NSUnknownKeyException
证明赋值
先证明 按照 setKey:、_setKey: 的顺序查找方法
YZPerson.h 和 YZPerson.m 如下
1 |
|
调用地方
1 | YZPerson *person = [[YZPerson alloc]init]; |
打印结果是
1 | KVCDemo[26389:357519] setAge: - 20 |
说明调用来的是setAge: 那如果 去掉 setAge: 呢
1 |
|
结果为:
1 | KVCDemo[26594:360894] _setAge: - 20 |
证明了 按照 setKey:、_setKey: 的顺序查找方法
证明 accessInstanceVariablesDirectly
- 如果accessInstanceVariablesDirectly 返回值为 NO 调用setValue:forUndefinedKey: 并抛出异常 NSUnknownKeyException
- 如果accessInstanceVariablesDirectly 返回值为 YES 就去查找成员变量,就直接赋值。
- 我们在 YZPerson.h 中定义四个成员变量, YZPerson.m中 只有accessInstanceVariablesDirectly 并返回NO
1 |
|
运行报错:找不到 key值 age
1 | KVCDemo[27163:369895] *** Terminating app due to uncaught exception |
- 我们把YZPerson.m中 只有accessInstanceVariablesDirectly 返回YES
运行结果:
1 | KVCDemo[27385:373752] 20 |
证明是按照_key、_isKey、key、isKey的顺序查找成员变量
代码还是上面的代码,打断点,然后LLDB调试
1 | (lldb) po person->_age |
如果去掉成员变量_age
结果为
1 |
|
同理其他的几种情况,读者可自行尝试 demo。
KVC与KVO
通过关于KVO看这篇就够了 我们知道
KVO的本质
- 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
- 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
- willChangeValueForKey:
- 父类原来的setter
- didChangeValueForKey:
- 内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)
那么KVC能否触发KVO呢,
我们在 YZPerson.h中书写如下代码
1 | @interface YZPerson : NSObject |
我们知道,成员变量是不会生成set 和 get方法的
然后 YZPerson.m中书写如下代码
1 |
|
在VC中设置KVO监听
1 |
|
输出结果为:
1 | KVCDemo[28271:388786] observeValueForKeyPath - { |
可知,其实在系统内部,是调用了
- willChangeValueForKey:
- didChangeValueForKey:
进一步验证
然后 YZPerson.m中书写如下代码
1 |
|
输出结果为:
1 |
|
所以,足以说明,KVC内部调用了 willChangeValueForKey 和 didChangeValueForKey
取值 valueForKey:的原理
- 按照getKey、key、isKey、_key的顺序查找方法
- 如果找到了,就直接调用
- 如果没找到,就查看accessInstanceVariablesDirectly 方法的返回值
- 如果accessInstanceVariablesDirectly 返回值为 NO 调用valueForUndefinedKey:并抛出异常NSUnknownKeyException
- 如果accessInstanceVariablesDirectly 返回值为 YES 按照_key、_isKey、key、isKey的顺序查找成员变量
- 如果找到了成员变量,就直接取值。
- 如果 _key、_isKey、key、isKey的顺序没有查找到成员变量就调用valueForUndefinedKey:并抛出异常NSUnknownKeyException
验证取值
- 取值和赋值的大体逻辑基本一致
然后 YZPerson.m中书写如下代码
1 |
|
VC中如下代码
1 | - (void)viewDidLoad { |
结果为
1 | KVCDemo[29145:403008] 取值为:11 |
如果去掉
1 | - (int)getAge |
则,输出结果为12。
上面验证了 按照getKey、key、isKey、_key的顺序查找方法
其他的验证逻辑,和赋值验证过程一致,就不赘述了。
本文相关代码github地址 github
本文参考资料: