KVO(Key-Value Observing)是 iOS/macOS 中用于实现观察者模式的一种机制,允许一个对象监听另一个对象特定属性的变化。要理解 KVO 的本质,需要从其实现原理入手。
一、KVO 的基本使用方式
在 Objective-C 或 Swift(通过 @objc dynamic)中,对一个对象的属性进行 KVO 监听通常包括以下步骤:
Objective-C 示例:
[object addObserver:self
forKeyPath:@"propertyName"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionsOld
context:NULL];
并在 observeValueForKeyPath:ofObject:change:context: 方法中处理变化。
Swift 示例(需继承自 NSObject 并使用 dynamic):
class MyObserver: NSObject {
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
// 处理变化
}
}
// 注册观察
obj.addObserver(observer, forKeyPath: "propertyName", options: [.new, .old], context: nil)
注意:Swift 中被观察的属性必须标记为
@objc dynamic,以便运行时能动态拦截。
二、KVO 的本质(实现原理)
KVO 的核心本质是 基于 runtime 动态生成子类并重写 setter 方法,从而在属性值变化时自动通知观察者。
具体过程如下:
1. 动态创建派生类(isa-swizzling)
当你第一次向一个对象添加 KVO 观察者时,系统会通过 Objective-C runtime 动态创建该对象类的一个匿名子类(例如:
NSKVONotifying_OriginalClassName)。然后将原对象的
isa指针指向这个新生成的子类(即 isa-swizzling),这样对象就“变成”了这个子类的实例。
2. 重写被观察属性的 setter 方法
在这个动态子类中,系统会重写被观察属性的 setter 方法。
新的 setter 方法内部会:
调用
willChangeValueForKey:调用父类(原始类)的 setter(真正赋值)
调用
didChangeValueForKey:
didChangeValueForKey:内部会触发observeValueForKeyPath:...回调,通知所有注册的观察者。
3. 自动管理观察者
KVO 会维护一个观察者列表(通过关联对象或其他内部结构),确保在合适时机(如对象释放前)移除观察者(但开发者仍需手动移除以避免崩溃)。
三、验证 KVO 的 isa-swizzling
你可以通过打印对象的类来验证:
NSLog(@"Before KVO: %@", object_getClass(target));
[target addObserver:...];
NSLog(@"After KVO: %@", object_getClass(target)); // 会显示 NSKVONotifying_XXX
四、注意事项
必须手动移除观察者,否则在 dealloc 时可能 crash(iOS 10+ 部分场景有自动移除,但不推荐依赖)。
被观察的属性必须是 KVC 兼容 的(即有合法的 getter/setter)。
在 Swift 中,只有
@objc dynamic属性才能被 KVO 监听,因为需要动态派发。KVO 是同步的,且发生在主线程(除非你在后台线程修改属性)。
总结
KVO 的本质是:利用 Objective-C runtime 的动态特性,在运行时生成子类并重写 setter 方法,通过 isa-swizzling 将对象“伪装”成子类实例,从而在属性变更时自动触发通知机制。
这是一种典型的“黑魔法”式设计,体现了 Objective-C runtime 的强大与灵活性。