KVO原理分析

概述

KVO全稱KeyValueObserving,翻譯成鍵值觀察,是蘋果提供的一套事件通知機制。允許對象監(jiān)聽另一個對象特定屬性的改變,并在改變時接收到事件。由于KVO的實現(xiàn)機制,所以對屬性才會發(fā)生作用,一般繼承自NSObject的對象都默認(rèn)支持KVO。

KVONSNotificationCenter都是iOS中觀察者模式的一種實現(xiàn)。區(qū)別在于,相對于被觀察者和觀察者之間的關(guān)系,KVO是一對一的,而不一對多的。KVO對被監(jiān)聽對象無侵入性,不需要修改其內(nèi)部代碼即可實現(xiàn)監(jiān)聽。

KVO可以監(jiān)聽單個屬性的變化,也可以監(jiān)聽集合對象的變化。通過KVCmutableArrayValueForKey:等方法獲得代理對象,當(dāng)代理對象的內(nèi)部對象發(fā)生改變時,會回調(diào)KVO監(jiān)聽的方法。集合對象包含NSArrayNSSet

基礎(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ù)是一個枚舉類型。如果傳入NSKeyValueObservingOptionNewNSKeyValueObservingOptionOld表示接收新值和舊值,默認(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)致Crashchange字典中存放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)行通信。

注意點

  • KVOaddObserverremoveObserver需要是成對的,如果重復(fù)remove則會導(dǎo)致NSRangeException類型的Crash,如果忘記remove則會在觀察者釋放后再次接收到KVO回調(diào)時Crash。
  • 蘋果官方推薦的方式是,在init的時候進(jìn)行addObserver,在deallocremoveObserver,這樣可以保證addremove是成對出現(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 機制聲稱的類。

可以參考用代碼探討 KVC/KVO 的實現(xiàn)原理這篇文章,通過代碼一步步分析,從斷點截圖來看,可以很好證明以上被重寫的方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 該文章屬于劉小壯原創(chuàng),轉(zhuǎn)載請注明:劉小壯[http://www.itdecent.cn/u/2de707c93d...
    劉小壯閱讀 49,047評論 35 227
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,674評論 1 32
  • 上半年有段時間做了一個項目,項目中聊天界面用到了音頻播放,涉及到進(jìn)度條,當(dāng)時做android時候處理的不太好,由于...
    DaZenD閱讀 3,102評論 0 26
  • 面試題目 iOS用什么方式實現(xiàn)對一個對象的KVO?(KVO的本質(zhì)是什么?)如何手動觸發(fā)KVO? 上面兩道面試題目,...
    FGNeverMore閱讀 542評論 0 1
  • 此刻,在中國大地上的某間教室里,黑板上醒目的倒計時數(shù)字已經(jīng)所剩不多,教室很安靜,除非數(shù)學(xué)老師加課講題。有一顆浮躁的...
    檸檬西柚酸牛奶閱讀 252評論 0 2

友情鏈接更多精彩內(nèi)容