概述
KVO全稱為Key Value Observing,鍵值監(jiān)聽(tīng)機(jī)制,由NSKeyValueObserving協(xié)議提供支持,NSObject類繼承了該協(xié)議,所以NSObject的子類都可使用該方法。
KVO全稱KeyValueObserving,是蘋果提供的一套事件通知機(jī)制。允許對(duì)象監(jiān)聽(tīng)另一個(gè)對(duì)象特定屬性的改變,并在改變時(shí)接收到事件。由于KVO的實(shí)現(xiàn)機(jī)制,所以對(duì)屬性才會(huì)發(fā)生作用,一般繼承自NSObject的對(duì)象都默認(rèn)支持KVO。
KVO和NSNotificationCenter都是iOS中觀察者模式的一種實(shí)現(xiàn)。區(qū)別在于,相對(duì)于被觀察者和觀察者之間的關(guān)系,KVO是一對(duì)一的,而一對(duì)多的。KVO對(duì)被監(jiān)聽(tīng)對(duì)象無(wú)侵入性,不需要修改其內(nèi)部代碼即可實(shí)現(xiàn)監(jiān)聽(tīng)。
KVO可以監(jiān)聽(tīng)單個(gè)屬性的變化,也可以監(jiān)聽(tīng)集合對(duì)象的變化。通過(guò)KVC的mutableArrayValueForKey:等方法獲得代理對(duì)象,當(dāng)代理對(duì)象的內(nèi)部對(duì)象發(fā)生改變時(shí),會(huì)回調(diào)KVO監(jiān)聽(tīng)的方法。集合對(duì)象包含NSArray和NSSet。
基礎(chǔ)使用
使用KVO分為三個(gè)步驟:
通過(guò)addObserver:forKeyPath:options:context:方法注冊(cè)觀察者,觀察者可以接收keyPath屬性的變化事件。
在觀察者中實(shí)現(xiàn)observeValueForKeyPath:ofObject:change:context:方法,當(dāng)keyPath屬性發(fā)生改變后,KVO會(huì)回調(diào)這個(gè)方法來(lái)通知觀察者。
當(dāng)觀察者不需要監(jiān)聽(tīng)時(shí),可以調(diào)用removeObserver:forKeyPath:方法將KVO移除。需要注意的是,調(diào)用removeObserver需要在觀察者消失之前,否則會(huì)導(dǎo)致Crash。
注冊(cè)方法
在注冊(cè)觀察者時(shí),可以傳入options參數(shù),參數(shù)是一個(gè)枚舉類型。如果傳入NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld表示接收新值和舊值,默認(rèn)為只接收新值。如果想在注冊(cè)觀察者后,立即接收一次回調(diào),則可以加入NSKeyValueObservingOptionInitial枚舉。
還可以通過(guò)方法context傳入任意類型的對(duì)象,在接收消息回調(diào)的代碼中可以接收到這個(gè)對(duì)象,是KVO中的一種傳值方式。
在調(diào)用addObserver方法后,KVO并不會(huì)對(duì)觀察者進(jìn)行強(qiáng)引用,所以需要注意觀察者的生命周期,否則會(huì)導(dǎo)致觀察者被釋放帶來(lái)的Crash。
監(jiān)聽(tīng)方法
觀察者需要實(shí)現(xiàn)observeValueForKeyPath:ofObject:change:context:方法,當(dāng)KVO事件到來(lái)時(shí)會(huì)調(diào)用這個(gè)方法,如果沒(méi)有實(shí)現(xiàn)會(huì)導(dǎo)致Crash。change字典中存放KVO屬性相關(guān)的值,根據(jù)options時(shí)傳入的枚舉來(lái)返回。枚舉會(huì)對(duì)應(yīng)相應(yīng)key來(lái)從字典中取出值,例如有NSKeyValueChangeOldKey字段,存儲(chǔ)改變之前的舊值。
change中還有NSKeyValueChangeKindKey字段,和NSKeyValueChangeOldKey是平級(jí)的關(guān)系,來(lái)提供本次更改的信息,對(duì)應(yīng)NSKeyValueChange枚舉類型的value。例如被觀察屬性發(fā)生改變時(shí),字段為NSKeyValueChangeSetting。
如果被觀察對(duì)象是集合對(duì)象,在NSKeyValueChangeKindKey字段中會(huì)包含NSKeyValueChangeInsertion、NSKeyValueChangeRemoval、NSKeyValueChangeReplacement的信息,表示集合對(duì)象的操作方式。
KVO實(shí)現(xiàn)步驟
1.注冊(cè)觀察者(為被觀察這指定觀察者以及被觀察者屬性)
/*
options: 有4個(gè)值,分別是:
NSKeyValueObservingOptionOld 把更改之前的值提供給處理方法
NSKeyValueObservingOptionNew 把更改之后的值提供給處理方法
NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法,一旦注冊(cè),立馬就會(huì)調(diào)用一次。通常它會(huì)帶有新值,而不會(huì)帶有舊值。
NSKeyValueObservingOptionPrior 分2次調(diào)用。在值改變之前和值改變之后。
*/
//注冊(cè)一個(gè)監(jiān)聽(tīng)器用于監(jiān)聽(tīng)指定的key路徑[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
2.實(shí)現(xiàn)回調(diào)方法
//當(dāng)key路徑對(duì)應(yīng)的屬性值發(fā)生改變時(shí),監(jiān)聽(tīng)器就會(huì)回調(diào)自身的監(jiān)聽(tīng)方法,如下- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)contex
}
3.觸發(fā)回調(diào)方法
注意:路徑keyPath請(qǐng)參考上一篇KVO中的說(shuō)明
4.移除觀察者
//刪除指定的key路徑監(jiān)聽(tīng)器
[self.person removeObserver:self forKeyPath:@"name"];
//刪除指定的key路徑監(jiān)聽(tīng)器,只是多了context參數(shù)
[self.person removeObserver:self forKeyPath:@"name" context:nil];
KVO的主要應(yīng)用場(chǎng)景
應(yīng)用場(chǎng)景:當(dāng)數(shù)據(jù)模型的數(shù)據(jù)發(fā)生改變時(shí),視圖組件能動(dòng)態(tài)的更新,及時(shí)顯示數(shù)據(jù)模型更新后的數(shù)據(jù)。
比如:監(jiān)聽(tīng)scrollView的contentOffset屬性,來(lái)完成用戶滾動(dòng)時(shí)動(dòng)態(tài)改變某些控件的屬性實(shí)現(xiàn)效果,包括漸變導(dǎo)航欄、下拉刷新控件等效果。
KVO的實(shí)現(xiàn)原理簡(jiǎn)要說(shuō)明
KVO 是基于 runtime 機(jī)制實(shí)現(xiàn)的 當(dāng)某個(gè)類的屬性對(duì)象第一次被觀察時(shí),系統(tǒng)就會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一 個(gè)派生類,在這個(gè)派生類中重寫基類中任何被觀察屬性的 setter 方法。派生類在 被重寫的 setter 方法內(nèi)實(shí)現(xiàn)真正的通知機(jī)制。
如果原類為 Person,那么生成的派生類名為 NSKVONotifying_Person 每個(gè)類對(duì)象中都有一個(gè) isa 指針指向當(dāng)前類,當(dāng)一個(gè)類對(duì)象的第一次被觀察,那么 系統(tǒng)會(huì)偷偷將 isa 指針指向動(dòng)態(tài)生成的派生類,從而在給被監(jiān)控屬性賦值時(shí)執(zhí)行的是派生類的 setter 方法。
鍵值觀察通知依賴于 NSObject 的兩個(gè)方法willChangeValueForKey:,didChangevlueForKey:
在一個(gè)被觀察屬性發(fā)生改變之前, willChangeValueForKey: 一定會(huì)被調(diào)用,這就 會(huì)記錄舊的值。而當(dāng)改變發(fā)生后,didChangeValueForKey: 會(huì)被調(diào)用,
繼而observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用。
補(bǔ)充:KVO 的這套實(shí)現(xiàn)機(jī)制中蘋果還偷偷重寫了 class 方法,讓我們誤認(rèn)為還是使用的當(dāng)前類,從而達(dá)到隱藏生成的派生類。
注意點(diǎn)
KVO的addObserver和removeObserver需要是成對(duì)的,如果重復(fù)remove則會(huì)導(dǎo)致NSRangeException類型的Crash,如果忘記remove則會(huì)在觀察者釋放后再次接收到KVO回調(diào)時(shí)Crash。