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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容

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