OC觀察者模式之KVO的使用與思考

轉載本文需註明出處:微信公衆號EAWorld,違者必究。html


引言:

不管用哪一種語言進行軟件開發,咱們都會接觸到設計模式,我的認爲設計模式存在的意義在於:在某些需求下,採用適合的設計模式,使代碼結構合理,從而提升代碼的可讀性、可擴展性、可移植性,此文將要討論的是OC開發中的一種經常使用模式之一:觀察者模式之KVO。
KVO俗稱鍵值觀察(key-value observe),鍵值觀察是當被觀察的對象屬性發生改變時,會通知到觀察對象的一種機制。

目錄:

一、KVO的做用
二、KVO的使用方法
三、KVO的實現原理
四、KVO與KVC、代理、通知的區別
五、KVO實現過程當中的注意事項

不管用哪一種語言進行軟件開發,咱們都會接觸到設計模式,我的認爲設計模式存在的意義在於:在某些需求下,採用適合的設計模式,使代碼結構合理,從而提升代碼的可讀性、可擴展性、可移植性,此文將要討論的是iOS開發中的一種經常使用模式之一:觀察者模式之KVO。咱們先看下官方文檔給的KVO介紹:



翻譯過來就是:KVO是運用isa混寫技術實現自動觀察鍵值的。isa指針是指向對象的類,本質上是指向類中的方法實現。當一個對象註冊觀察者時,這個對象的isa指針被修改指向一箇中間類。永遠不要用isa來判斷一個類的繼承關係,而是應該用class方法來判斷類的實例。

KVO俗稱鍵值觀察(key-value observe),鍵值觀察是當被觀察的對象屬性發生改變時,會通知到觀察對象的一種機制。

1.KVO的做用

一、監聽帶有狀態的基礎控件,如開關、按鈕等;

二、監聽字符串的改變,當監聽的字符串改變時,來作一些自定義的操做;

三、當數據模型的數據發生改變時,視圖組件能動態的更新,及時顯示數據模型更新後的數據,好比tableview中數據發生變化進行刷新列表操做,監聽 scrollView的contentOffset屬性監聽頁面的滑動.

2.KVO的使用方法

KVO的使用可分爲自動監聽和手動監聽。

1.自動監聽

1.1自動監聽操做步驟:

(1)添加觀察者
(2)在觀察者中添加觀察鍵值方法
(3)在dealloc中移除監聽

1.2示例代碼:

建立兩個類ModelA和ModelB,兩個類中都添加屬性「des」,在控制器中,將B添加爲A的觀察者。代碼以下:

ModelA中代碼:




ModelB中代碼:




控制器中代碼:



控制器中添加觀察者的方法調用的是以下的類方法:

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString 
*)keyPath options:(NSKeyValueObservingOptions)options 
context:(nullable void *)context

各個參數說明:

@param observer 被監聽的對象
@param keyPath 被監聽對象的屬性名,不可爲空,爲空崩潰
@param options 有4種
(1)NSKeyValueObservingOptionNew 把更改以前的值提供給處理方法
(2)NSKeyValueObservingOptionOld 把更改以後的值提供給處理方法
(3)NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法,一旦註冊,立馬就會調用一次。一般它會帶有新值,而不會帶有舊值。
(4)NSKeyValueObservingOptionPrior 分2次調用。在值改變以前和值改變以後
@param context 上下文

上述示例代碼的運行結果以下所示:



2.手動監聽



意思就是說:當某些須要控制監聽過程的場景下,就須要手動監聽,好比:爲了儘可能減小沒必要要的觸發通知操做,或者當多個更改同時具有的時候才調用屬性改變的監聽方法。

實現手動監聽的要點主要包括這幾部分:


a.重寫

(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key

b.在set方法中在賦值的先後分別調用

willChangeValueForKey和didChangeValueForKey


2.1實現部分屬性的手動監聽


在animal.h中添加兩個屬性age和name,在animal.m中關閉age的自動監聽功能,其它屬性依然能夠自動監聽,在控制其中實現添加按鈕點擊按鈕的時候改變age的值,並觸發監聽方法,代碼以下:

animal類:



要實現類方法 automaticallyNotifiesObserversForKey,並在其中設置對特定的 key 不自動發送通知(返回 NO 便可)。這裏要注意,對其它非手動實現的 key,要轉交給 super 來處理[1,2,3]。

控制器:





當不點擊按鈕的時候,打印結果只打印了name屬性的值:



當點擊按鈕以後,會手動觸發監聽,打印結果以下:



2.2全部屬性都手動監聽(禁止自動監聽)

若是須要禁用該類KVO的話直接automaticallyNotifiesObserversForKey返回NO。

將animal.m中的類方法修改以後:



運行以後不點擊按鈕的話,age和name屬性都不會自動調用監聽方法:


 segmentfault

點擊了按鈕以後,只有實現了手動監聽的age屬性調用了監聽方法:設計模式


3.KVO的實現原理


當某一個類的實例第一次使用KVO的時候,系統就會在運行期間動態的建立該類的一個派生類,該類的命名規則通常是以NSKVONotifying爲前綴,以本來的類名爲後綴。而且將原型的對象的isa指針指向該派生類。同時在派生類中重載了使用KVO的屬性的setter方法,在重載的setter方法中實現真正的通知機制,正如前面咱們手動實現KVO同樣。這麼作是基於設置屬性會調用setter方法,而經過重寫就得到了 KVO 須要的通知機制。固然前提是要經過遵循 KVO 的屬性設置方式來變動屬性值,若是僅是直接修改屬性對應的成員變量,是沒法實現 KVO 的[4,5]。

4.KVO與KVC、代理、通知的區別

1.與KVC的不一樣?

KVC,便是指 NSKeyValueCoding,一個非正式的 Protocol,提供一種機制來間接訪問對象的屬性,而不是經過調用Setter、Getter方法等 顯式的存取方式去訪問。KVO 就是基於 KVC 實現的關鍵技術之一。

KVO,即Key-Value Observing,它提供一種機制,當指定的對象的屬性被修改後,對象就會接受到通知。

2.與delegate的不一樣?

和delegate同樣,KVO和NSNotification的做用都是類與類之間的通訊。可是與delegate不一樣的是:這兩個都是負責發送接收通知,剩下的事情由系統處理,因此不用返回值;而delegate 則須要通訊的對象經過變量(代理)聯繫;delegate只是一對一,而這兩個能夠一對多。delegate是很是嚴格的語法,須要定義不少代碼。


3.和notification的區別?

notification比KVO多了發送通知的一步。二者都是一對多,可是對象之間直接的交互,notification明顯得多,須要notificationCenter來作爲中間交互。而KVO如咱們介紹的,設置觀察者->處理屬性變化,至於中間通知這一環,則隱祕多了,只留一句「交由系統通知」,具體的可參照以上實現過程的剖析。notification的優勢是監聽不侷限於屬性的變化,還能夠對多種多樣的狀態變化進行監聽,監聽範圍廣,例如鍵盤、先後臺等系統通知的使用也更顯靈活方便[6,7]。

5.KVO實現過程當中的注意事項

iOS 10如下會有這些狀況,iOS11不會出現這些狀況,可是爲了代碼的嚴謹性,以及以防出現沒法預知的錯誤,仍是避開這些比較好。

一、添加觀察者次數與remove次數不匹配致使程序崩潰

連續對同一屬性添加觀察者是能夠的,可是也要保證在移除觀察者的時候也要移除對應次,否則可能會引起崩潰(iOS11以上不會崩潰)。

當對同一個keypath進行兩次removeObserver時會致使程序crash,這種狀況經常出如今父類有一個kvo,父類在dealloc中remove了一次,子類又remove了一次的狀況下。不要覺得這種狀況不多出現!當你封裝framework開源給別人用或者多人協做開發時是有可能出現的,並且這種crash很難發現。不知道你發現沒,目前的代碼中context字段都是nil,那可否利用該字段來標識出到底kvo是superClass註冊的,仍是self註冊的?咱們能夠分別在父類以及本類中定義各自的context字符串,而後在dealloc中remove observer時指定移除的自身添加的observer。這樣iOS就能知道移除的是本身的kvo,而不是父類中的kvo,避免二次remove形成crash[8]。

二、移除不存在的觀察者(iOS11以上不會崩潰)

當某個對象並無添加觀察者時,卻執行了移除觀察者的操做,也會致使程序崩潰,此處不附相關代碼。

三、被觀察者銷燬時還存在觀察者(iOS11以上不會崩潰)

這種狀況常出如今複雜邏輯下,觀察者先於被觀察者銷燬[9]

四、KVO 行爲是同步的,而且發生與所觀察的值發生變化的一樣的線程上。沒有隊列或者 Run-loop 的處理。手動或者自動調用 -didChange… 會觸發 KVO 通知。

因此,當咱們試圖從其餘線程改變屬性值的時候咱們應當十分當心,除非能肯定全部的觀察者都用線程安全的方法處理 KVO 通知。一般來講,咱們不推薦把 KVO 和多線程混起來。若是咱們要用多個隊列和線程,咱們不該該在它們互相之間用 KVO[10]。


參考資料:
[1]https://www.jianshu.com/p/d447660bed7e
[2]https://www.jianshu.com/p/91c41292b5b9
[3]https://www.jianshu.com/p/5a1c58aacb23
[4]https://www.cnblogs.com/yang-shuai/p/8556326.html
[5]https://www.cnblogs.com/PSSSCode/p/5506577.html 
[6]http://www.mamicode.com/info-detail-515516.html
[7]https://www.songma.com/news/txtlist_i38955v.html
[8]https://www.cnblogs.com/wengzilin/p/4346775.html
[9]https://segmentfault.com/a/1190000016896055
[10]https://www.jianshu.com/p/b9f020a8b4c9

關於做者:小幸運,iOS軟件開發工程師,參與普元Dev客戶端OC代碼的維護及新功能開發;使用普元移動開發平臺開發郵政移動平臺項目郵我行app。熱愛互聯網技術,努力拓寬技術面的程序媛一枚。



關於EAWorld:微服務,DevOps,數據治理,移動架構原創技術分享。長按二維碼關注!安全

相關文章
相關標籤/搜索