6-11.【OC】【KVC/KVO】KVO 添加 observer 时,Runtime 会做哪些事情?

2 阅读2分钟

当你在 Objective-C 中调用 addObserver:forKeyPath:options:context: 时,Runtime 并不仅仅是做个登记,而是通过 isa-swizzling 技术对被观察对象的类结构进行了一场“动态手术”。

以下是 Runtime 内部执行的精密步骤:


1. 动态派生 KVO 子类 (NSKVONotifying_...)

Runtime 首先检查被观察对象 obj 的类 A。如果该对象是第一次被观察,Runtime 会利用 objc_allocateClassPair 动态创建一个名为 NSKVONotifying_A 的新类。

  • 继承关系: 这个新类继承自原类 A
  • 命名规律: 始终以 NSKVONotifying_ 作为前缀。

2. 修改对象的 isa 指针

这是 KVO 最核心的“调包”动作。Runtime 会将对象 objisa 指针指向这个新创建的子类。

  • 结果: 该对象从此变成了一个派生类的实例。当你对这个对象调用方法时,系统会优先在派生类中查找。

3. 在子类中重写被观察属性的 Setter

Runtime 会在新类中重写对应属性的 setter 方法(例如 setName:)。这个重写的 setter 是通知分发的关键,其内部实现通常调用了 _NSSetObjectValueAndNotify 等函数,逻辑如下:

  1. 调用 willChangeValueForKey:
  2. 调用父类(原类)的真正 setter 实现。
  3. 调用 didChangeValueForKey:(该方法内部会触发观察者的回调)。

4. 重写辅助方法以“瞒天过海”

为了让开发者感觉不到对象已经变了,Runtime 会在子类中重写几个关键方法:

  • -class 内部实现返回原类Class 对象。这让你在调试时调用 [obj class] 依然看到的是 A
  • -dealloc 负责在对象销毁时进行一些内部清理工作。
  • -_isKVOA 这是一个私有标志位方法,返回 YES,用于 Runtime 内部快速判断一个对象是否正在被监听。

5. 存储观察信息

Runtime 会将被观察的 KeyPath、观察者(Observer)指针、配置项(Options)以及 Context 存储在对象的一个私有内部存储区域(通常是关联对象或全局的哈希表)。


总结对照:注册前后的变化

特性注册 KVO 前注册 KVO 后
isa 指针指向原类 A指向动态子类 NSKVONotifying_A
Setter 方法原类实现(直接修改内存/变量)子类重写实现(包装了通知发送逻辑)
class 方法返回 A返回 A (被重写以隐藏子类)
运行时类名ANSKVONotifying_A (可通过 object_getClass 发现)