概述
KVO全稱KeyValueObserving,翻譯成鍵值觀察,是蘋果提供的一套事件通知機制。允許對象監(jiān)聽另一個對象特定屬性的改變,并在改變時接收到事件。由于KVO的實現(xiàn)機制,所以對屬性才會發(fā)生作用,一般繼承自NSObject的對象都默認(rèn)支持KVO。
KVO和NSNotificationCenter都是iOS中觀察者模式的一種實現(xiàn)。區(qū)別在于,相對于被觀察者和觀察者之間的關(guān)系,KVO是一對一的,而不一對多的。KVO對被監(jiān)聽對象無侵入性,不需要修改其內(nèi)部代碼即可實現(xiàn)監(jiān)聽。
KVO可以監(jiān)聽單個屬性的變化,也可以監(jiān)聽集合對象的變化。通過KVC的mutableArrayValueForKey:等方法獲得代理對象,當(dāng)代理對象的內(nèi)部對象發(fā)生改變時,會回調(diào)KVO監(jiān)聽的方法。集合對象包含NSArray和NSSet。
基礎(chǔ)使用
使用KVO分為三個步驟:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
1.通過addObserver:forKeyPath:options:context:方法注冊觀察者,觀察者可以接收keyPath屬性的變化事件。
2.在觀察者中實現(xiàn)observeValueForKeyPath:ofObject:change:context:方法,當(dāng)keyPath屬性發(fā)生改變后,KVO會回調(diào)這個方法來通知觀察者。
3.當(dāng)觀察者不需要監(jiān)聽時,可以調(diào)用removeObserver:forKeyPath:方法將KVO移除。需要注意的是,調(diào)用removeObserver需要在觀察者消失之前,否則會導(dǎo)致Crash。
注冊方法
- 在注冊觀察者時,可以傳入options參數(shù),參數(shù)是一個枚舉類型。如果傳入
NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld表示接收新值和舊值,默認(rèn)為只接收新值。如果想在注冊觀察者后,立即接收一次回調(diào),則可以加入NSKeyValueObservingOptionInitial枚舉。 - 還可以通過方法
context傳入任意類型的對象,在接收消息回調(diào)的代碼中可以接收到這個對象,是KVO中的一種傳值方式。 - 在調(diào)用
addObserver方法后,KVO并不會對觀察者進(jìn)行強引用,所以需要注意觀察者的生命周期,否則會導(dǎo)致觀察者被釋放帶來的Crash。
監(jiān)聽方法
- 觀察者需要實現(xiàn)
observeValueForKeyPath:ofObject:change:context:方法,當(dāng)KVO事件到來時會調(diào)用這個方法,如果沒有實現(xiàn)會導(dǎo)致Crash。change字典中存放KVO屬性相關(guān)的值,根據(jù)options時傳入的枚舉來返回。枚舉會對應(yīng)相應(yīng)key來從字典中取出值,例如有NSKeyValueChangeOldKey字段,存儲改變之前的舊值。 -
change中還有NSKeyValueChangeKindKey字段,和NSKeyValueChangeOldKey是平級的關(guān)系,來提供本次更改的信息,對應(yīng)NSKeyValueChange枚舉類型的value。例如被觀察屬性發(fā)生改變時,字段為NSKeyValueChangeSetting。 - 如果被觀察對象是集合對象,在
NSKeyValueChangeKindKey字段中會包含NSKeyValueChangeInsertion、NSKeyValueChangeRemoval、`NSKeyValueChangeReplacement的信息,表示集合對象的操作方式。
實際應(yīng)用
KVO主要用來做鍵值觀察操作,想要一個值發(fā)生改變后通知另一個對象,則用KVO實現(xiàn)最為合適。通過KVO在Model和Controller之間進(jìn)行通信。
注意點
-
KVO的addObserver和removeObserver需要是成對的,如果重復(fù)remove則會導(dǎo)致NSRangeException類型的Crash,如果忘記remove則會在觀察者釋放后再次接收到KVO回調(diào)時Crash。 - 蘋果官方推薦的方式是,在init的時候進(jìn)行
addObserver,在dealloc時removeObserver,這樣可以保證add和remove是成對出現(xiàn)的,是一種比較理想的使用方式。
手動調(diào)用KVO
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
可能有時候,我們要實現(xiàn)手動的KVO,或者我們實現(xiàn)的類庫不希望被KVO。
這時候需要關(guān)閉自動生成KVO通知,然后手動的調(diào)用,手動通知的好處就是,可以靈活加上自己想要的判斷條件。下面看個例子如下:
- (void)setBalance:(double)theBalance {
if (theBalance != _balance) {
[self willChangeValueForKey:@"balance"];
_balance = theBalance;
[self didChangeValueForKey:@"balance"];
}
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"balance"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
如果想控制當(dāng)前對象的自動調(diào)用過程,也就是由上面兩個方法發(fā)起的KVO調(diào)用,則可以重寫automaticallyNotifiesObserversForKey:方法。方法返回YES則表示可以調(diào)用,如果返回NO則表示不可以調(diào)用。
KVO實現(xiàn)原理
KVO是通過isa-swizzling技術(shù)實現(xiàn)的(這句話是整個KVO實現(xiàn)的重點)。在運行時根據(jù)原類創(chuàng)建一個中間類,這個中間類是原類的子類,并動態(tài)修改當(dāng)前對象的isa指向中間類。并且將class方法重寫,返回原類的Class。所以蘋果建議在開發(fā)中不應(yīng)該依賴isa指針,而是通過class實例方法來獲取對象類型。
即當(dāng)一個類型為 ObjectA 的對象,被添加了觀察后,系統(tǒng)會生成一個 NSKVONotifying_ObjectA 類,并將對象的isa指針指向新的類,也就是說這個對象的類型發(fā)生了變化。這個類相比較于ObjectA,會重寫以下幾個方法。
- 1.重寫setter。因為 KVO 的原理是修改 setter 方法,因此使用 KVO 必須調(diào)用 setter 。若直接訪問屬性對象則沒有效果。
- 2.重寫class。當(dāng)修改了isa指向后,class的返回值不會變,但isa的值則發(fā)生改變。
- 3.重寫dealloc。系統(tǒng)重寫 dealloc 方法來釋放資源。
- 4.重寫_isKVOA。 這個私有方法是用來標(biāo)示該類是一個 KVO 機制聲稱的類。