KVO 的全称是 Key-Value Observing, 也称 "键值监听", 可以用于监听某个对象属性值的改变.
使用 KVO
通常我们通过 addObserver:forKeyPath:options:context 来监听某个实例的某个属性变化. 当该属性的值发生变化的时候会通过 - (**void**)observeValueForKeyPath:(NSString *)keyPath ofObject:(**id**)object change:(NSDictionary<NSKeyValueChangeKey,**id**> *)change context:(**void** *)context 通知我们.
显然实现这个功能的基础是属性值得改变, 而属性值得改变需要调用 setter 方法. 可是即使 setter 方法是我们重写的, 并且在其实现中并没有编写关于监听属性改变通知监听的代码, KVO 依然可以起作用. 这是为什么呢?
KVO 实现原理
我们都知道一个类的实例方法存在于其类对象中, 当一个类的实例向实例方法发送调用消息时候, 会通过类实例的 isa 指针去其类对象中找到其对应的实例方法执行. setter 方法就存在于类对象中.
新的类对象
我们通过 runtime 的 object_getClass()直接利用实例对象的 isa 指针查找实例的类对象.
这里之所以使用 runtime 获取类对象而不使用消息发送机制, 是因为 class 是通过消息发送查找到 class 方法, 在 class 方法中返回的类对象, 而 class 方法可能被重写.
通过上图我们可以看出, 使用 KVO 监听的属性的 isa 指向的类对象已经不是 Person 了, 而是一个继承自 Person 的一个新的类 NSKVONotifying_Person, 我们并没有创建过这个类, 它是 objc 的 runtime 动态生成的.
NSKVONotifying_Person 重写了父类的 setAge:,class,dealloc 方法, 并且新增了一个_isKVOA 方法.
NSKVONotifying_Person 的 setter 方法
我们可以看出在为 person 实例添加 observer 之前和之后的 setAge: 方法的实现发生了变化, 之前是直接调用类对象中存储的实例方法, 添加之后调用了 Foundation 框架中_NSSetIntValueAndNotify 函数.
之所以是_NSSet*IntValue*AndNotify 是因为 age 属性我们定义的是 int 类型, 若是其他类型则会相应更改
在_NSSetIntValueAndNotify 方法中, 会调用 willChangeValueForKey, 然后调用父类的 setAge 方法, 再调用 didChangeValueForKey 方法. 在 didChangeValueForKey 中通知属性改变, 从而使得 observeValueForKeyPath 得到消息.
NSKVONotifying_Person 的 class 方法
在我们获取 person 实例的类对象的时候, 我们发现使用 runtime 的 object_getClass 和使用消息发送机制 [_person class] 得到的结果不一样, 这是因为 NSKVONotifying_Person 重写了 class 方法的实现, 在实现中调用了[super class], 而 NSKVONotifying_Person 又是 Person 的子类, 所以获取到的 Person.
NSKVONotifying_Person 的 dealloc 方法
由于 runtime 在实例对象添加了 KVO 之后动态创建了类和一些对象, 所以可能会在 dealloc 中回收这些资源.
NSKVONotifying_Person 的_isKVOA 方法
_isKVOA 应该是会返回一个 BOOL 类型的值, 表示是否使用了 KVO.
手动触发 KVO
通过上面我们可以得知, KVO 的实现主要是因为在 setter 方法中调用_NSSetIntValueAndNotify, 而它调用 willChangeValueForKey, 然后调用父类的 setAge 方法, 再调用 didChangeValueForKey 方法, didChangeValueForKey 告诉 observeValueForKeyPath 属性改变. 所以我们可以通过实现 will 和 did 方法手动出发 KVO.
成员变量会不会触发 KVO
通过上面原理我们可以知道, KVO 主要是通过重写 setter 方法实现的, 而成员变量并不会实现 setter 方法, 所以成员变量的改变并不会触发 KVO.
来源: https://juejin.im/post/5c495ae3f265da615877781d