當(dāng)觀察某對(duì)象 A 時(shí)涯肩,KVO 機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象A當(dāng)前類(lèi)的子類(lèi),并為這個(gè)新的子類(lèi)重寫(xiě)了被觀察屬性 keyPath 的 setter 方法巢钓。setter 方法隨后負(fù)責(zé)通知觀察對(duì)象屬性的改變狀況病苗。
深入剖析:
Apple 使用了 isa 混寫(xiě)(isa-swizzling)來(lái)實(shí)現(xiàn) KVO 。當(dāng)觀察對(duì)象A時(shí)症汹,KVO機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)新的名為:NSKVONotifying_A 的新類(lèi)硫朦,該類(lèi)繼承自對(duì)象A的本類(lèi),且 KVO 為 NSKVONotifying_A 重寫(xiě)觀察屬性的 setter 方法背镇,setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后咬展,通知所有觀察對(duì)象屬性值的更改情況。
(備注: isa 混寫(xiě)(isa-swizzling)isa:is a kind of 芽世; swizzling:混合挚赊,攪合;)
①NSKVONotifying_A 類(lèi)剖析:在這個(gè)過(guò)程济瓢,被觀察對(duì)象的 isa 指針從指向原來(lái)的 A 類(lèi)荠割,被 KVO 機(jī)制修改為指向系統(tǒng)新創(chuàng)建的子類(lèi) NSKVONotifying_A 類(lèi),來(lái)實(shí)現(xiàn)當(dāng)前類(lèi)屬性值改變的監(jiān)聽(tīng)旺矾;
所以當(dāng)我們從應(yīng)用層面上看來(lái)蔑鹦,完全沒(méi)有意識(shí)到有新的類(lèi)出現(xiàn),這是系統(tǒng)“隱瞞”了對(duì) KVO 的底層實(shí)現(xiàn)過(guò)程箕宙,讓我們誤以為還是原來(lái)的類(lèi)嚎朽。但是此時(shí)如果我們創(chuàng)建一個(gè)新的名為“NSKVONotifying_A”的類(lèi),就會(huì)發(fā)現(xiàn)系統(tǒng)運(yùn)行到注冊(cè) KVO 的那段代碼時(shí)程序就崩潰柬帕,因?yàn)橄到y(tǒng)在注冊(cè)監(jiān)聽(tīng)的時(shí)候動(dòng)態(tài)創(chuàng)建了名為 NSKVONotifying_A 的中間類(lèi)哟忍,并指向這個(gè)中間類(lèi)了。
(isa 指針的作用:每個(gè)對(duì)象都有 isa 指針陷寝,指向該對(duì)象的類(lèi)锅很,它告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類(lèi)是什么。所以對(duì)象注冊(cè)為觀察者時(shí)凤跑,isa 指針指向新子類(lèi)爆安,那么這個(gè)被觀察的對(duì)象就神奇地變成新子類(lèi)的對(duì)象(或?qū)嵗┝恕?/strong>) 因而在該對(duì)象上對(duì) setter 的調(diào)用就會(huì)調(diào)用已重寫(xiě)的 setter,從而激活鍵值通知機(jī)制仔引。
—>我猜扔仓,這也是 KVO 回調(diào)機(jī)制褐奥,為什么都俗稱(chēng)KVO技術(shù)為黑魔法的原因之一吧:內(nèi)部神秘、外觀簡(jiǎn)潔翘簇。
②子類(lèi)setter方法剖析:KVO 的鍵值觀察通知依賴(lài)于 NSObject 的兩個(gè)方法:willChangeValueForKey:和 didChangevlueForKey:撬码,在存取數(shù)值的前后分別調(diào)用 2 個(gè)方法:
被觀察屬性發(fā)生改變之前,willChangeValueForKey:被調(diào)用缘揪,通知系統(tǒng)該 keyPath 的屬性值即將變更耍群;當(dāng)改變發(fā)生后, didChangeValueForKey: 被調(diào)用找筝,通知系統(tǒng)該 keyPath 的屬性值已經(jīng)變更蹈垢;之后, observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用袖裕。且重寫(xiě)觀察屬性的 setter 方法這種繼承方式的注入是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的曹抬。
KVO 為子類(lèi)的觀察者屬性重寫(xiě)調(diào)用存取方法的工作原理在代碼中相當(dāng)于:
-(void)setName:(NSString *)newName{
[self willChangeValueForKey:@"name"]; //KVO 在調(diào)用存取方法之前總調(diào)用
[super setValue:newName forKey:@"name"]; //調(diào)用父類(lèi)的存取方法
[self didChangeValueForKey:@"name"]; //KVO 在調(diào)用存取方法之后總調(diào)用
}
三、特點(diǎn):
觀察者觀察的是屬性急鳄,只有遵循 KVO 變更屬性值的方式才會(huì)執(zhí)行 KVO 的回調(diào)方法谤民,例如是否執(zhí)行了 setter 方法、或者是否使用了 KVC 賦值疾宏。
如果賦值沒(méi)有通過(guò) setter 方法或者 KVC张足,而是直接修改屬性對(duì)應(yīng)的成員變量,例如:僅調(diào)用 _name = @"newName"坎藐,這時(shí)是不會(huì)觸發(fā) KVO 機(jī)制为牍,更加不會(huì)調(diào)用回調(diào)方法的。
所以使用 KVO 機(jī)制的前提是遵循 KVO 的屬性設(shè)置方式來(lái)變更屬性值岩馍。
四碉咆、步驟
- 1.注冊(cè)觀察者,實(shí)施監(jiān)聽(tīng)蛀恩;
- 2.在回調(diào)方法中處理屬性發(fā)生的變化疫铜;
- 3.移除觀察者
五.實(shí)現(xiàn)方法(蘋(píng)果 API 文檔中的方法):
A.注冊(cè)觀察者:
//第一個(gè)參數(shù) observer:觀察者 (這里觀察self.myKVO對(duì)象的屬性變化)
//第二個(gè)參數(shù) keyPath: 被觀察的屬性名稱(chēng)(這里觀察 self.myKVO 中 num 屬性值的改變)
//第三個(gè)參數(shù) options: 觀察屬性的新值、舊值等的一些配置(枚舉值双谆,可以根據(jù)需要設(shè)置壳咕,例如這里可以使用兩項(xiàng))
//第四個(gè)參數(shù) context: 上下文,可以為 KVO 的回調(diào)方法傳值(例如設(shè)定為一個(gè)放置數(shù)據(jù)的字典)
[self.myKVO addObserver:self
forKeyPath:@"num"
options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew
context:nil];
B. 屬性(keyPath)的值發(fā)生變化時(shí)顽馋,收到通知谓厘,調(diào)用以下方法:
//keyPath:屬性名稱(chēng)
//object:被觀察的對(duì)象
//change:變化前后的值都存儲(chǔ)在 change 字典中
//context:注冊(cè)觀察者時(shí),context 傳過(guò)來(lái)的值
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context
{
}