基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

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" ...] 时:

  1. Runtime 会在内存中动态生成一个 Person 的子类。
  2. 这个子类的命名规则通常是:NSKVONotifying_ + 原始类名(例如:NSKVONotifying_Person)。

第二步:ISA-Swizzling (指针修正)

  1. 系统将 person 对象的 isa 指针从指向 Person 类,修改为指向 NSKVONotifying_Person 类。
  2. 结果:虽然这个对象表面上还是 Person(因为继承关系),但实际执行代码时,它使用的是 NSKVONotifying_Person 的方法列表。

第三步:重写 Setter 方法

这是 KVO 最关键的一步。中间类 NSKVONotifying_Person 会重写被观察属性(例如 name)的 setName: 方法。

重写后的 setter 伪代码逻辑如下:

plaintext
- (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. 中间类重写的其他方法

除了 setterclass 方法,中间类通常还会重写以下方法:

  1. dealloc
    • 用于做收尾工作,处理引用关系等。
  2. _isKVO
    • 这是一个私有方法,通常返回 YES,用来标记该类是一个 KVO 机制生成的中间类。

4. 移除观察者 (removeObserver)

当你调用 removeObserver 时:

  1. 对象的 isa 指针会重新指回原始类 (Person)。
  2. 对象恢复成普通的实例,不再具备监听功能。
  3. 注意:动态生成的中间类 NSKVONotifying_Person 通常不会立即销毁,而是留在内存中,以便下次有同类对象添加 KVO 时复用,避免重复创建类的开销。

5. 验证 KVO 原理的代码

你可以通过以下代码验证上述原理:

plaintext
// 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: 来触发通知。

00:00
00:00