KVO实现原理

KVO

  1. KVO 是 Key-Value-Observing 的简称。

  2. KVO 是一个观察者模式。观察一个对象的属性,注册一个指定的路径,若这个对象的的属性被修改,则 KVO 会自动通知观察者。

  3. 更通俗的话来说就是任何对象都允许观察其他对象的属性,并且可以接收其他对象状态变化的通知。

KVO 基本使用

1.// 注册观察者,实施监听;
[self.person addObserver:self
              forKeyPath:@"age"
                 options:NSKeyValueObservingOptionNew
                 context:nil];

2.// 回调方法,在这里处理属性发生的变化;
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context

3.// 移除观察者;
[self removeObserver:self forKeyPath:@“age"];

KVO 在 Apple 中的 API 文档如下:

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 …

Apple 使用了 isa 搅拌技术(isa-swizzling)来实现的 KVO 。当一个观察者注册对象的一个属性 isa 观察对象的指针被修改,指着一个中间类而不是在真正的类。

isa 指针的作用:每个对象都有 isa 指针,指向该对象的类,它告诉 runtime 系统这个对象的类是什么。

注:如果对 runtime 不很清楚的话可以看下这篇文章Objective-C 中的 Runtime

举个栗子:

_person = [[Person alloc] init];

/**
 *  添加观察者
 *
 *  @param observer 观察者
 *  @param keyPath  被观察的属性名称
 *  @param options  观察属性的新值、旧值等的一些配置(枚举值,可以根据需要设置,例如这里可以使用两项)
 *  @param context  上下文,可以为nil。
 */
[_person addObserver:self
          forKeyPath:@"age"
             options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
             context:nil];
/**
 *  KVO回调方法
 *
 *  @param keyPath 被修改的属性
 *  @param object  被修改的属性所属对象
 *  @param change  属性改变情况(新旧值)
 *  @param context context传过来的值
 */
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context
{
    NSLog(@"%@对象的%@属性改变了:%@",object,keyPath,change);
 }
/**
 *  移除观察者
 */
- (void)dealloc
{
    [self.person removeObserver:self forKeyPath:@"age"];
}

KVO 实现原理

当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。 派生类在被重写的 setter 方法实现真正的通知机制,就如前面手动实现键值观察那样。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。 同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。

派生类 NSKVONotifying_Person 剖析:

在这个过程,被观察对象的 isa 指针从指向原来的 Person 类,被 KVO 机制修改为指向系统新创建的子类 NSKVONotifying_Person 类,来实现当前类属性值改变的监听。

所以当我们从应用层面上看来,完全没有意识到有新的类出现,这是系统“隐瞒”了对 KVO 的底层实现过程,让我们误以为还是原来的类。但是此时如果我们创建一个新的名为 NSKVONotifying_Person 的类(),就会发现系统运行到注册 KVO 的那段代码时程序就崩溃,因为系统在注册监听的时候动态创建了名为 NSKVONotifying_Person 的中间类,并指向这个中间类了。

因而在该对象上对 setter 的调用就会调用已重写的 setter,从而激活键值通知机制。这也是 KVO 回调机制,为什么都俗称 KVO 技术为黑魔法的原因之一吧:内部神秘、外观简洁。

子类 setter 方法剖析:

KVO 在调用存取方法之前总是调用 willChangeValueForKey:,通知系统该 keyPath 的属性值即将变更。 当改变发生后,didChangeValueForKey: 被调用,通知系统该 keyPath 的属性值已经变更。 之后,observeValueForKey:ofObject:change:context: 也会被调用。

重写观察属性的 setter 方法这种方式是在运行时而不是编译时实现的。 KVO 为子类的观察者属性重写调用存取方法的工作原理在代码中相当于:

- (void)setName:(NSString *)newName
{
    [self willChangeValueForKey:@"name"];    // KVO在调用存取方法之前总调用
    [super setValue:newName forKey:@"name"]; // 调用父类的存取方法
    [self didChangeValueForKey:@"name"];     // KVO在调用存取方法之后总调用
}

总结: KVO 的本质就是监听对象的属性进行赋值的时候有没有调用 setter 方法

  1. 系统会动态创建一个继承于 Person 的 NSKVONotifying_Person
  2. person 的 isa 指针指向的类 Person 变成 NSKVONotifying_Person,所以接下来的 person.age = newAge 的时候,他调用的不是 Person 的 setter 方法,而是 NSKVONotifying_Person(子类)的 setter 方法
  3. 重写NSKVONotifying_Person的setter方法:[super setName:newName]
  4. 通知观察者告诉属性改变。

KVO 应用

监听 ScrollView 的 contentOffSet 属性,采取相应的措施:

[scrollview addObserver:self
             forKeyPath:@“contentOffset                   
                options:NSKeyValueObservingOptionNew
                context:nil];

下面是用 KVO 写的一个通过监听 scrollview 的 contentOffSet 实现的一个小刷新功能,感兴趣的可以看下。

gif

KVO 总结

KVO 是一个对象能观察另一个对象属性的值,KVO 适合任何对象监听另一个对象的改变,这是一个对象与另外一个对象保持同步的一种方法。KVO 只能对属性做出反应,不会用来对方法或者动作做出反应。

优点:

  1. 提供一个简单的方法来实现两个对象的同步。
  2. 能够提供观察的属性的新值和旧值。
  3. 每一次属性值改变都是自动发送通知,不需要开发者手动实现。
  4. 用 keypath 来观察属性,因此也可以观察嵌套对象。

缺点:

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

推荐阅读更多精彩内容

  • KVC 什么是KVC KVC是Key-Value-Coding 的简称。 KVC是一种可以直接通过字符串的名字ke...
    Jack_deng阅读 975评论 0 0
  • 前言 Key-Value-Observer,它来源于观察者模式, 其基本思想(copy于某度)是一个目标对象管理所...
    CholMay阅读 3,378评论 6 18
  • 作者:wangzz原文地址:http://blog.csdn.net/wzzvictory/article/det...
    反调唱唱阅读 1,114评论 0 5
  • 一、概述 KVO,即:Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,则其观察...
    DeerRun阅读 10,038评论 11 33
  • 一、概述 二、KVO基本原理 1.KVO是基于runtime机制实现的2.当某个类的属性对象第一次被观察时,系统就...
    ForScanf阅读 715评论 0 0