KVO & KVC

由于oc的语言特性,使得开发者根本不必进行任何操作就可以进行属性的动态读写,这种方式就是Key Value Coding(简称KVC)。

KVC的操作方法由NSKeyValueCoding协议提供,而NSObject就实现了这个协议,也就是说OC中几乎所有的对象都支持KVC操作。

如果是动态设置属性,则优先考虑调用setA方法。如果没有该方法则优先考虑搜索成员变量_a,如果仍然不存在则搜索成员变量a,如果最后仍然没有搜索到这会调用这个类的setValue:forUndefinedKey:方法。在搜索过程中,不管这些方法、成员变量是私有还是公共的都能正确设置。

如果是动态读取属性,则优先调用a的getter方法,如果没有搜索到则会优先搜索成员变量_a,如果仍然不存在则会搜索成员变量a,如果仍然没搜索到就会调用这个类的valueforUndefinedKey:方法。而且,在搜索过程中,不管这些方法、成员变量是私有的还是公有的都能正确读取。

调用方式

kvo调用 

关于容器类(如:NSMutableArray)的观察, 当通过addObject: 向数组中添加对象, 不会触发KVO, 因为并没有触发set方法,解决方法: 通过KVC 方法 - mutableArrayValueForKey:

key 与 KeyPath要区分开来,key 可以从一个对象中获取值,而KeyPath可以将多个 key 用点号 "." 分割连接起来 . 比如:@"account.name" 相当于

 [p valuefForKey:@"account"]   valuefForKey:@"name" ];

KVO全称KeyValueObserving,是苹果提供的一套事件通知机制,主要用来做键值观察操作,当一个对象的属性值发生改变后,通知观察对象变并触发事件回调。依赖于Runtime机制来实现。

KVO是一对一的,NSNotification 是一对多的。 KVO对被监听对象无侵入性,不需要修改其内部代码即可实现监听。一般继承自NSObject的对象都默认支持KVO。

KVO和NSNotificationCenter都是iOS中观察者模式的一种实现。通知和kvo 对 观察者对象都是弱持有,避免引起循环引用 。

addObserver方法,KVO并不会对观察者进行强引用,需要注意观察者的生命周期,否则会导致观察者被释放带来的Crash。


面试题:如何避免循环引用???

self 持有了observer,保存kvo--观察者的数组持有了观察的observer,而self(被观察对象)又持有了保存kvo信息的数组,这就相当于是被观察对象持有了观察者。导致循环引用

方法: 对 观察者observer属性的声明使用 weak关键字。数组对内部的元素都是强引用,添加中间类 model 模型 ,让观察者 observer 作为model 的一个属性,使用weak 修饰,此时model 就是弱持有observer。再把 数组中存放model。 


使用KVO分为三个步骤:

1. 通过 addObserver:forKeyPath:options:context: 方法注册观察者,观察者可以接收keyPath属性的变化事件。

options参数枚举类型: 如果传入NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld表示接收新值和旧值,默认为只接收新值。如果想在注册观察者后,立即接收一次回调,则可以加入NSKeyValueObservingOptionInitial枚举。

context可传入任意类型的对象,在接收消息回调的代码中可以接收到这个对象,是KVO中的一种传值方式。还可以用来精准区分父类和子类同时对一个属性进行观察时所需要做的不同处理。

2. 在观察者中实现 observeValueForKeyPath:ofObject:change:context:方法,当keyPath属性发生改变后,KVO会回调这个方法来通知观察者。如果没有实现会导致Crash。

3. 当观察者不需要监听时,可以调用removeObserver:forKeyPath:方法将KVO移除,一般在delloc 中移除。

KVO的  addObserver  和  removeObserver  需要是成对的,如果重复  remove  会导致NSRangeException 类型的  Crash。在调用  addObserver  方法后,KVO并不会对观察者进行强引用,所以需要注意观察者的生命周期,否则会导致观察者被释放带来的Crash。


KVO基本原理: 当对象注册观察者时,对象的ISA指针被修改,指向中间类,而不是原来真正的类

举例说明:(监听一个Person类底层实现)

1. 系统在运行期动态的创建 Person 类的子类NSKVONotifying_Person. 

2. 修改当前对象的isa指针 ->  指向子类 NSKVONotifying_Person;

3. 只要调用对象的set, 顺着isa 指向 会调用NSKVONotifying_Person的 set 方法;此时的isa指针指的是子类,而不是原生类。

4. 重写NSKVONotifying_Person 的 set方法,方法的实现分三步:

1. 调用willChangeValueForKey:方法 

2. 调用super 的 setAge:  方法,真正的改变属性的值

3. 调用didChangeValueForKey: 方法,通知监听者属性值已经改变,执行监听者的  observeValueForKeyPath  方法。

NSKVONotifying_A 类中有 isKVOA 方法,可以判断当前类是否是KVO动态生成的类,可以从方法列表中搜索这个方法,是 KVO类 的一个标记。


NSKVONotifying_Person 对外默认是隐藏的。

1 . 使用 runtime 的  object_getClass() 方法:  object_getClass(person) ,结果是  NSKVONotifying_Person,反应了真实类型。

2 . 直接调用实例对象的 class 方法:[person  class], 结果是 Person

NSKVONotifying_Person 这个类重写了 class 方法,直接返回父类的 class  类型:[Person class]。因此应该使用class 方法来确认对象的真实类型

系统方法

 getIsa( )  为获取 isa 指向的类。

注意: isa 指针不总是指向实例对象所属的类,不能依靠它来确定类型,而是应该用 class 方法来确定实例对象的类。因为KVO的实现机理就是将被观察对象的 isa 指针指向一个中间类而不是真实的类,这是一种叫做isa-swizzling的技术。


KVO的本质是set方法,只有调用了set方法才会触发KVO。

如果直接修改属性对应的成员变量,不会触发 KVO。

例如:_age = 10;  就不会触发KVO。

键值观察通知触发依赖于NSObject的两个方法:系统默认是自动触发的,会调用下面3个方法:

1. willChangeValueForKey:  //被 观察属性发生改变之前调用,记录旧的值

2. observeValueForKey:ofObject:change:context: 

3. didChangeValueForKey:  // 改变发生后调用。


如何手动触发KVO ?

注册后,KVO默认会自动通知观察者。

如果想取消自动通知,改为自己手动控制。需要实现方法,并且需要自己的 set方法中手动触发

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString*)key。 //  返回NO可取消系统自动通知。  默认返回YES 

手动触发


 前提:需要关闭系统的自动提示方法,如上,让automaticallyNotifiesObserversForKey方法返回NO。

手动调用 willChangeValueForKey 和 didChangeValueForKey 方法。

方法实现



底层实现


参考:

iOS-底层原理 23:KVO 底层原理 - 简书.  👍🏻👍🏻👍🏻👍🏻👍🏻

KVO详解(二) - 腾讯云开发者社区-腾讯云 自己实现kvo 步骤,👍🏻👍🏻👍🏻👍🏻👍🏻

KVO用法总结 - Null959_的博客 - CSDN博客  详细讲解观察者注册,实现,移除的方法中的每个参数的具体含义  

KVO的本质 - 简书  可以从现象倒推KVO在运行时都做了什么 

KVO实现剖析  可以帮助我们理解KVO内部的实现细节 

对于KVO,你真的了解么? - CocoaChina_一站式开发者成长社区

KVO的本质 - 简书

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

推荐阅读更多精彩内容