KVO 底层原理与手动实现

Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects.

KVO 提供了一种对象监听机制,当被观察对象发生改变的时候,观察者会收到通知,但是使用过 KVO 的朋友都知道,我们通常并不需要在被观察对象里边写任何代码。既然这样,KVO 是怎样监听属性的呢?

KVO 原理

还是先来看下官方文档怎么说:

Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...

苹果透露 KVO 的实现使用了 isa-swizzling 技术。

当我们使用 KVO 去观察一个对象的时候,一个新的类会在运行时创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。重写的 setter 方法会负责在调用原 setter 方法之前和之后,去通知所有的观察者。然后通过 isa-swizzling 把这个被观察对象的** isa 指针指向新创建的子类**,于是,被观察对象就这样变成了新创建的子类的实例了。

当一个属性的值发生改变的时候,setter 方法会被调用,所以,去通知观察者的这件事情发生在属性的 setter 方法里。事实上,键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey:didChangevlueForKey:. 在一个被观察属性发生改变之前, willChangeValueForKey: 首先会被调用,该方法负责记录旧的值。而当改变发生后, observeValueForKey:ofObject:change:context:会被调用,最后调用 didChangeValueForKey: .

伪码表示:

- (void)setFoo:(id)foo
{
    [self willChangeValueForKey];
    _foo = foo;
    [self observeValueForKey:key ofObject:target change:CHANGE context:nil];
    [self didChangeValueForKey];
}

这也意味着通过“直接访问”(比如 _foo 而不是 self.foo)的方式去设置属性值是不会触发 KVO 的。

此外,除了 setter 被重写外,苹果还重写了 -class ,让其返回原来所属的那个类,比如说 [ _foo class ] 返回的是原来所属的类而不是新创建的子类。

我们还可以利用运行时去深入挖掘 KVO 的底层实现,这里推荐阅读 Mike Ash 的这篇文章

KVO 缺陷

关于 KVO 的一直存在很多的争议,详见NSHipsterKVO Considered HarmfulKey-Value Observing Done Right.

总结一下 KVO 的缺陷主要有以下几个方面:

  • API 设计缺陷。NSNotification 的 API 允许我们传入 selector( -addObserver: selector: name: object: ),这样收到通知的时候就可以调用到 selector ;相反使用 KVO 的话,我们必须在观察者类当中重写 -observeValueForKeyPath:ofObject:change:context: 方法,我们不得不在这个方法里加很多判断逻辑写一坨代码来监听不同的属性。
  • 在 -addObserver:forKeyPath:options:context: 里传入 NSString 对象作为 keyPath,编译器是无法检测错误的,如果属性名被更改了,这个观察就没意义了。这个问题可以通过 NSStringFromSelector 稍稍改善。另外,传给 context 的参数大部分情况下都是 nil.
  • 如果父类和子类都监听了同一个对象的同一个属性,很容易出现父类中remove了一次,子类又remove了一次的情况,应用程序第二次 remove 就会抛出异常然后 crash. 解决方案见 KVO Considered Harmful .
  • KVO 可能会导致死循环。死循环发生的场景是:你在-observeValueForKeyPath 方法里边设置了属性,而这个属性刚好被自己监听。
  • 某些情况下使用 KVO 监听属性会失效。比如说被监听属性的 setter 被 hook 了;还有一种情况是,对 weak 属性监听,如果 weak 实例变量指向的对象被释放了,weak 修饰的实例变量会自动设为 nil,KVO 也就失效了。

坑很多,KVO 使用的时候要特别地小心。

KVO 手动实现

KVO 使用的时候要注意的地方有很多,嫌麻烦我们可以自己撸一个玩。
实现思路可以去看 Glow 团队的这篇文章,我就不搬砖了。基于类似的思路,我实现了一个能够传入 selector 参数的 KVO. Demo 放在我的 github 上。

参考文章:
How Key-Value Observing is actually implemented at the runtime level
如何自己动手实现 KVO
KVO Considered Harmful
Key Value Observing
Key-Value Observing Done Right
Creating Classes at Runtime in Objective-C
Associated Objects

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 本文分为2个部分:概念与应用。概念部分旨在剖析 KVO 这一设计模式的实现原理;应用部分通过创建的项目,以说明 K...
    啊左阅读 57,983评论 107 438
  • iOS--KVO的实现原理与具体应用 长时间不用容易忘,这篇文章挺好的.转载自看本文分为2个部分:概念与应用。概念...
    超_iOS阅读 1,452评论 0 17
  • 一、概述 KVO,即:Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,则其观察...
    DeerRun阅读 10,134评论 11 33
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,814评论 0 9
  • 上半年有段时间做了一个项目,项目中聊天界面用到了音频播放,涉及到进度条,当时做android时候处理的不太好,由于...
    DaZenD阅读 3,046评论 0 26