johnhao
johnhao
发布于 2017-07-04 / 2 阅读
0

iOS-KVO

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 的强大与灵活性。