对源码探究部分写在这: 其他: KVO源码分析(GNUStep 版)
本文主要弄清楚苹果是怎么实现 KVO 机制的, 其中部分知识涉及到 runtime, 可以看我这个专题 二. runtime 怎么实现封装 | runtime 的基础数据结构
正式开始前,先上一张总的流程图, 图中不同颜色代表不同函数, 这里主要聊流程. 源码分析部分在另一篇文章中

KVO 是怎么实现的
在我们已知的 KVO 使用中, 它会先注册监听, 然后在”对象”属性发生变化时, 发出相应的通知.
KVO 实现监听行为, 分为两步:
- 将对象的 isa 指向改为新生成的一个类
- 将类中, 对应字段的 Setter 方法, 改为带通知的
(技术名词称之为isa-swizzling)
结合 runtime 的知识, 我们知道
- ”对象”本身是只带 isa 字段的结构体指针.
struct objc_object *obj - “对象”的 isa 指向另一个结构体变量
- 这个变量中存放了”对象”的执行函数表, 成员变量表等等.
所以, 只要修改 isa 的指向, 我们就能做到让一个对象执行函数时, 不走原先函数的办法.
同时, KVO 的目的不是改变我们声明好的类与对象. 所以其使用的方式是, 创建一个继承于原类的子类. 这样能保证新生成的类行为与之前的一致.
最后, 再把 KVO 想要达到的目的, “在属性修改时发出通知”. 所直接关联的属性 Setter 函数做修改, 偷梁换柱成自己定好的即可
比如将一个字符属性 setter, 改为setterChar, 内部这样实现
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
KVO 在源码中的实现方式
首先, Foundation 没有开源, 后续对 KVO 的探索都基于同源的 GNUStep (不记得什么地方, 官方提到过, 两边的接口是尽量保持一致的)
所以下面的流程并非基于 Foundation 源码, 仅作为参照, 了解 KVO 实现思路用
主体流程就是最上面的图, 具体源码分析这里不谈, 主要还看这里:
其他: KVO源码分析(GNUStep 版)
缓存:
KVO 提供了一系列的全局静态变量, 用于记录当前监听过的 class, 监听过的信息info, 是否初始化过kvoLock等
在实际的运行中, 一旦发现有缓存在, 便不会重新执行一次生成逻辑, 而是改为从已有的缓存中获取
全局有一个叫 observationInfo的东西, 里面存放了监听者的信息.
要注意的跟 KVO 有关的很多数据结构都被标记为 not retained, 这是为何 obc 销毁前没取消监听会 crash 的原因
/* An instance of this records all the information for a single observation.
*/
@interface GSKVOObservation : NSObject
{
@public
NSObject *observer; // Not retained (zeroing weak pointer)
void *context;
int options;
}
@end
/*
* Instances of this class are created to hold information about the
* observers monitoring a particular object which is being observed.
*/
@interface GSKVOInfo : NSObject
{
NSObject *instance; // Not retained.
NSRecursiveLock *iLock;
NSMapTable *paths;
}
流程:
- 初始化全局缓存的初始化, 调用函数
setup() - 生成替换类: 使用 self, 使用字符串拼接的方式生成对应 替换类, 调用
replacementForClass - 使用 self, 创建对应的类 调用 runtime 中的
objc_allocateClassPair - copy self 中的成员变量表,(while 遍历实现)
- copy self 中的函数表
- 后续代码中没有看到 copy 其他内容(这还挺奇怪的)
- 将 isa 改为替换类: 调用
object_setClass实现 - 根据监听的属性类型, 替换属性的 setter 方法
从实现方式中得出的一些点
- 流程中生成的替换类是原类的子类
- 因为 KVO 是通过重写 setter 实现的, 并且新的 setter 中会调用原类的 setter, 所以
- 所有通过 setter 触发的属性修改, 都会触发监听, 包括 KVC 设值
- 对于没有 setter 的成员变量无效
- KVO 的通知是通过 setter 中
willChangeValueForKey,didChangeValueForKey实现, 所以如果手动调用这两个函数是可以触发对应的监听方法的 - 实际操作中, 修改的 set 函数包含
set两种_set