思考:
- iOS 用什么方式实现对一个对象的 KVO?(KVO的本质是什么?)
- 如何手动触发 KVO ?
- 直接修改成员变量会触发 KVO 么?
- 通过 KVC 修改属性会触发 KVO 么?
- KVC 的赋值和取值过程是怎样的?原理是什么?
KVO 监听
KVO 的全称是 Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变。
定义 Person、Observer
打印结果:
person2 和 person1 拥有同一个类对象,修改属性 age 时调用的是同一个类对象里的对象方法 -(void)setAge
。
未使用 KVO 监听的对象
- 思考:person2 修改 age 时为什么在 Observer 里没有打印?
因为 person2 没有使用 KVO 监听,修改属性 age 时调用的是 Person 类对象里的对象方法 -(void)setAge
,所以在 Observer 里没有打印。
使用了 KVO 监听的对象
打印 person1、person2 类对象:
打印结果:
从打印结果看出,因为 person1 使用了 KVO 监听,所以系统通过 runtime 生成了一个NSKVONotifying_Person 对象,所以 person1 修改属性 age 时调用的是 生成了一个NSKVONotifying_Person 类对象里的对象方法 -(void)setAge
。
NSKVONotifying_Person 类对象
打印对象方法 -(void)setAge
的地址:
打印结果:
从打印结果看出,person1 添加 KVO 监听前后,对象方法 -(void)setAge
的地址变了。
打印地址对应的方法名:
从打印结果看出,person1 添加 KVO 监听后,修改 age 时调用的是 Foundation 框架里的 _NSSetIntValueAndNotify 方法。
_NSSetIntValueAndNotify 方法
_NSSetIntValueAndNotify 方法实现猜想:
查看 _NSSet*AndNotify 的存在:
_NSSet*ValueAndNotify 的内部实现:
- 调用
willChangeValueForKey:
- 调用原来的 setter 实现
- 调用
didChangeValueForKey:
,其内部会调用 observer 的observeValueForKeyPath:ofObject:change:context:
方法
NSKVONotifying_Person 元类对象
打印 person1 的元类对象:
打印结果:
NSKVONotifying_Person 类对象的 isa 指针指向的是 NSKVONotifying_Person 元类对象。
修改成员变量的值是否会触发 KVO
|
|
运行后并没有出现打印。虽然 person 添加了 KVO 监听,但是修改 age 时并没有调用 -(void)setAge
方法。
KVC
KVC 的全称是 Key-Value Coding,俗称“键值编码”,可以通过一个 key 来访问某个属性。常见的API有:
setValue: forKey: 原理
定义 Person:
通过你 kvc 修改 age 的值:
依次注释掉 setAge:
、_setAge:
方法,可以发现 setValue:forKey:
会优先调用 setAge:
,setAge:
不存在时会调用 _setAge:
方法。如果 setAge:
和 _setAge:
都不存在时,会调用 +(BOOL)accessInstanceVariablesDirectly
方法判断是否可以访问成员变量。
打断点后,可以在控制台看到 _age、_isAge、age、isAge 被依次赋值:
KVC 触发 KVO
定义 Person:
打印结果:
setKey 和 _setKey 存在
添加 KVO 监听时会调用一次 +(BOOL)accessInstanceVariablesDirectly
, 调用 setValue:forKey:
时会调用一次 +(BOOL)accessInstanceVariablesDirectly
,再去调用 willChangeValueForKey
和 didChangeValueForKey
。
添加 KVO 监听第一次调用 accessInstanceVariablesDirectly 方法:
setValue:forKey: 第一次调用 accessInstanceVariablesDirectly 方法:
setKey 和 _setKey 不存在
添加 KVO 监听时会调用两次 + (BOOL)accessInstanceVariablesDirectly
, 调用 setValue:forKey:
时会调用两次 + (BOOL)accessInstanceVariablesDirectly
,再去调用 willChangeValueForKey
和 didChangeValueForKey
。
添加 KVO 监听第一次调用 accessInstanceVariablesDirectly 方法:
添加 KVO 监听第二次调用 accessInstanceVariablesDirectly 方法:
setValue:forKey: 第一次调用 accessInstanceVariablesDirectly 方法:
setValue:forKey: 第二次调用 accessInstanceVariablesDirectly 方法:
valueForKey: 原理
定义 Person:
依次注释掉 - (int)getAge
、- (int)age
、- (int)isAge
、- (int)_age
方法,从打印结果可以发现,setValue:forKey:
方法会优先调用 - (void)setAge:(int)age
,- (void)setAge:(int)age
不存在时会调用 - (void)_setAge:(int)age
方法,以此类推。
setKey 和 _setKey 存在
添加 KVO 监听第一次调用 accessInstanceVariablesDirectly 方法:
setKey 和 _setKey 不存在
添加 KVO 监听第一次调用 accessInstanceVariablesDirectly 方法:
valueForKey: 第一次调用 accessInstanceVariablesDirectly 方法:
总结
- iOS 用什么方式实现对一个对象的 KVO?(KVO的本质是什么?)
利用 RuntimeAPI 动态生成一个子类,并且让 instance 对象的 isa 指向这个全新的子类。
当修改 instance 对象的属性时,会调用 Foundation 的 _NSSetXXXValueAndNotify 函数:123willChangeValueForKey:父类原来的setterdidChangeValueForKey:
didChangeValueForKey: 内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)
如何手动触发 KVO ?
手动调用 willChangeValueForKey: 和 didChangeValueForKey:123willChangeValueForKey:person->age = 10;didChangeValueForKey:直接修改成员变量会触发 KVO 么?
不会触发,修改 age 时并没有调用 -(void)setAge 方法。通过 KVC 修改属性会触发 KVO 么?
会触发KVO。通过KVC修改属性会调用 willChangeValueForKey: 和 didChangeValueForKey: 方法,而 didChangeValueForKey: 方法内部会触发 KVO 监听。KVC 的赋值和取值过程是怎样的?原理是什么?
赋值:setValue:forKey:
会按照setKey:
/_setKey:
顺序查找方法,如果方法存在,直接调用方法赋值。如果方法不存在,会调用accessInstanceVariablesDirectly
方法,判断是否可以访问成员变量。如果可以,会按照_key
/_isKey
/key
/isKsy
顺序查找成员变量,找到后赋值。如果不可以访问成员变量,或者成员变量不存在,就会调用setValue:forUndefinedKey:
并抛出异常 NSUnknownKeyException。取值:
valueForKey:
会按照getKey
/key
/isKey
/_key
顺序查找方法,如果方法存在,直接调用方法取值。如果方法不存在,会调用accessInstanceVariablesDirectly
方法,判断是否可以访问成员变量。会按照_key
/_isKey
/key
/isKsy
顺序查找成员变量,找到成员变量后取值。如果不可以访问成员变量,或者成员变量不存在,就会调用valueForUndefinedKey:
并抛出异常 NSUnknownKeyException。