KVO (Key-Value Observing) 的底层实现原理?(ISA-Swizzling、中间类)
KVO (Key-Value Observing) 的底层实现完全依赖于 Objective-C 的 Runtime(运行时)机制。其核心技术点就是你提到的 ISA-Swizzling(ISA 指针混写) 和 动态生成的中间类(Intermediate Class)。
下面我将分步骤详细解析 KVO 的底层原理:
1. 核心原理概述
当一个对象(假设为 person,类为 Person)的属性被注册观察(addObserver)时,系统会在运行时动态地创建一个 Person 类的子类(中间类),并将 person 对象的 isa 指针指向这个新的子类。
这个子类重写了被观察属性的 setter 方法,在赋值前后插入了通知代码,从而实现自动通知。
2. 详细实现步骤
第一步:动态生成中间类 (NSKVONotifying_ClassName)
当你调用 [person addObserver:self forKeyPath:@"name" ...] 时:
- Runtime 会在内存中动态生成一个
Person的子类。 - 这个子类的命名规则通常是:
NSKVONotifying_+原始类名(例如:NSKVONotifying_Person)。
第二步:ISA-Swizzling (指针修正)
- 系统将
person对象的isa指针从指向Person类,修改为指向NSKVONotifying_Person类。 - 结果:虽然这个对象表面上还是
Person(因为继承关系),但实际执行代码时,它使用的是NSKVONotifying_Person的方法列表。
第三步:重写 Setter 方法
这是 KVO 最关键的一步。中间类 NSKVONotifying_Person 会重写被观察属性(例如 name)的 setName: 方法。
重写后的 setter 伪代码逻辑如下:
- (void)setName:(NSString *)name {
// 1. 通知系统该属性即将改变
[self willChangeValueForKey:@"name"];
// 2. 调用父类(原始类)的 setter 方法进行实际赋值
// 这一步保证了属性值真的被修改了
[super setName:name];
// 3. 通知系统该属性已经改变
[self didChangeValueForKey:@"name"];
}
didChangeValueForKey:内部会触发观察者的observeValueForKeyPath:ofObject:change:context:回调。
第四步:重写 class 方法 (伪装)
为了对开发者隐藏 KVO 的底层实现,中间类重写了 class 方法。
- 如果不重写,
[person class]会返回NSKVONotifying_Person。 - 重写后,
[person class]返回的是 原始父类Person。
这也就是为什么你打印 [person class] 还是显示 Person,但通过 Runtime 函数 object_getClass(person) 看到的却是 NSKVONotifying_Person。
3. 中间类重写的其他方法
除了 setter 和 class 方法,中间类通常还会重写以下方法:
dealloc:- 用于做收尾工作,处理引用关系等。
_isKVO:- 这是一个私有方法,通常返回
YES,用来标记该类是一个 KVO 机制生成的中间类。
- 这是一个私有方法,通常返回
4. 移除观察者 (removeObserver)
当你调用 removeObserver 时:
- 对象的
isa指针会重新指回原始类 (Person)。 - 对象恢复成普通的实例,不再具备监听功能。
- 注意:动态生成的中间类
NSKVONotifying_Person通常不会立即销毁,而是留在内存中,以便下次有同类对象添加 KVO 时复用,避免重复创建类的开销。
5. 验证 KVO 原理的代码
你可以通过以下代码验证上述原理:
// 1. 打印添加 KVO 之前的类信息
NSLog(@"Before: class method = %@, runtime class = %@",
[self.person class],
object_getClass(self.person));
// 输出: Person, Person
// 2. 添加 KVO
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
// 3. 打印添加 KVO 之后的类信息
NSLog(@"After: class method = %@, runtime class = %@",
[self.person class],
object_getClass(self.person));
// 输出: Person, NSKVONotifying_Person <-- 验证了 ISA-Swizzling 和 class 方法重写
// 4. 打印 setter 方法的实现地址 (IMP)
IMP setterImp = [self.person methodForSelector:@selector(setName:)];
NSLog(@"Setter IMP: %p", setterImp);
// 这里打印的地址是 Foundation 框架中 KVO 的实现,而不是 Person 类原本的 setter 地址
总结
KVO 的底层原理概括为一句话:
利用 Runtime 动态生成子类 (NSKVONotifying_XXX),通过 ISA-Swizzling 将对象的 isa 指向该子类,并在子类中重写 setter 方法,在赋值前后手动调用 willChangeValueForKey: 和 didChangeValueForKey: 来触发通知。