OC觀察者模式之KVO的使用與思考
引言:
無論用哪種語言進行軟件開發(fā),我們都會接觸到設計模式,個人認為設計模式存在的意義在于:在某些需求下,采用適合的設計模式,使代碼結(jié)構(gòu)合理,從而提高代碼的可讀性、可擴展性、可移植性,此文將要討論的是OC開發(fā)中的一種常用模式之一:觀察者模式之KVO。
KVO俗稱鍵值觀察(key-value observe),鍵值觀察是當被觀察的對象屬性發(fā)生改變時,會通知到觀察對象的一種機制。
目錄:
1、KVO的作用
2、KVO的使用方法
3、KVO的實現(xiàn)原理
4、KVO與KVC、代理、通知的區(qū)別
5、KVO實現(xiàn)過程中的注意事項
無論用哪種語言進行軟件開發(fā),我們都會接觸到設計模式,個人認為設計模式存在的意義在于:在某些需求下,采用適合的設計模式,使代碼結(jié)構(gòu)合理,從而提高代碼的可讀性、可擴展性、可移植性,此文將要討論的是iOS開發(fā)中的一種常用模式之一:觀察者模式之KVO。我們先看下官方文檔給的KVO介紹:
翻譯過來就是:KVO是運用isa混寫技術(shù)實現(xiàn)自動觀察鍵值的。isa指針是指向?qū)ο蟮念,本質(zhì)上是指向類中的方法實現(xiàn)。當一個對象注冊觀察者時,這個對象的isa指針被修改指向一個中間類。永遠不要用isa來判斷一個類的繼承關(guān)系,而是應該用class方法來判斷類的實例。
KVO俗稱鍵值觀察(key-value observe),鍵值觀察是當被觀察的對象屬性發(fā)生改變時,會通知到觀察對象的一種機制。
1.KVO的作用
1、監(jiān)聽帶有狀態(tài)的基礎控件,如開關(guān)、按鈕等;
2、監(jiān)聽字符串的改變,當監(jiān)聽的字符串改變時,來做一些自定義的操作;
3、當數(shù)據(jù)模型的數(shù)據(jù)發(fā)生改變時,視圖組件能動態(tài)的更新,及時顯示數(shù)據(jù)模型更新后的數(shù)據(jù),比如tableview中數(shù)據(jù)發(fā)生變化進行刷新列表操作,監(jiān)聽 scrollView的contentOffset屬性監(jiān)聽頁面的滑動.
2.KVO的使用方法
KVO的使用可分為自動監(jiān)聽和手動監(jiān)聽。
1.自動監(jiān)聽
1.1自動監(jiān)聽操作步驟:
(1)添加觀察者
(2)在觀察者中添加觀察鍵值方法
(3)在dealloc中移除監(jiān)聽
1.2示例代碼:
創(chuàng)建兩個類ModelA和ModelB,兩個類中都添加屬性“des”,在控制器中,將B添加為A的觀察者。代碼如下:
ModelA中代碼:
ModelB中代碼:
控制器中代碼:
控制器中添加觀察者的方法調(diào)用的是如下的類方法:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
各個參數(shù)說明:
@param observer 被監(jiān)聽的對象
@param keyPath 被監(jiān)聽對象的屬性名,不可為空,為空崩潰
@param options 有4種
(1)NSKeyValueObservingOptionNew 把更改之前的值提供給處理方法
(2)NSKeyValueObservingOptionOld 把更改之后的值提供給處理方法
(3)NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法,一旦注冊,立馬就會調(diào)用一次。通常它會帶有新值,而不會帶有舊值。
(4)NSKeyValueObservingOptionPrior 分2次調(diào)用。在值改變之前和值改變之后
@param context 上下文
上述示例代碼的運行結(jié)果如下所示:
2.手動監(jiān)聽
意思就是說:當某些需要控制監(jiān)聽過程的場景下,就需要手動監(jiān)聽,比如:為了盡量減少不必要的觸發(fā)通知操作,或者當多個更改同時具備的時候才調(diào)用屬性改變的監(jiān)聽方法。
實現(xiàn)手動監(jiān)聽的要點主要包括這幾部分:
a.重寫
(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
b.在set方法中在賦值的前后分別調(diào)用
willChangeValueForKey和didChangeValueForKey
2.1實現(xiàn)部分屬性的手動監(jiān)聽
在animal.h中添加兩個屬性age和name,在animal.m中關(guān)閉age的自動監(jiān)聽功能,其它屬性依然可以自動監(jiān)聽,在控制其中實現(xiàn)添加按鈕點擊按鈕的時候改變age的值,并觸發(fā)監(jiān)聽方法,代碼如下:
animal類:
要實現(xiàn)類方法 automaticallyNotifiesObserversForKey,并在其中設置對特定的 key 不自動發(fā)送通知(返回 NO 即可)。這里要注意,對其它非手動實現(xiàn)的 key,要轉(zhuǎn)交給 super 來處理[1,2,3]。
控制器:
當不點擊按鈕的時候,打印結(jié)果只打印了name屬性的值:
當點擊按鈕之后,會手動觸發(fā)監(jiān)聽,打印結(jié)果如下:
2.2所有屬性都手動監(jiān)聽(禁止自動監(jiān)聽)
如果需要禁用該類KVO的話直接automaticallyNotifiesObserversForKey返回NO。
將animal.m中的類方法修改之后:
運行之后不點擊按鈕的話,age和name屬性都不會自動調(diào)用監(jiān)聽方法:
點擊了按鈕之后,只有實現(xiàn)了手動監(jiān)聽的age屬性調(diào)用了監(jiān)聽方法:
3.KVO的實現(xiàn)原理
當某一個類的實例第一次使用KVO的時候,系統(tǒng)就會在運行期間動態(tài)的創(chuàng)建該類的一個派生類,該類的命名規(guī)則一般是以NSKVONotifying為前綴,以原本的類名為后綴。并且將原型的對象的isa指針指向該派生類。同時在派生類中重載了使用KVO的屬性的setter方法,在重載的setter方法中實現(xiàn)真正的通知機制,正如前面我們手動實現(xiàn)KVO一樣。這么做是基于設置屬性會調(diào)用setter方法,而通過重寫就獲得了 KVO 需要的通知機制。當然前提是要通過遵循 KVO 的屬性設置方式來變更屬性值,如果僅是直接修改屬性對應的成員變量,是無法實現(xiàn) KVO 的[4,5]。
4.KVO與KVC、代理、通知的區(qū)別
1.與KVC的不同?
KVC,即是指 NSKeyValueCoding,一個非正式的 Protocol,提供一種機制來間接訪問對象的屬性,而不是通過調(diào)用Setter、Getter方法等 顯式的存取方式去訪問。KVO 就是基于 KVC 實現(xiàn)的關(guān)鍵技術(shù)之一。
KVO,即Key-Value Observing,它提供一種機制,當指定的對象的屬性被修改后,對象就會接受到通知。
2.與delegate的不同?
和delegate一樣,KVO和NSNotification的作用都是類與類之間的通信。但是與delegate不同的是:這兩個都是負責發(fā)送接收通知,剩下的事情由系統(tǒng)處理,所以不用返回值;而delegate 則需要通信的對象通過變量(代理)聯(lián)系;delegate只是一對一,而這兩個可以一對多。delegate是非常嚴格的語法,需要定義很多代碼。
3.和notification的區(qū)別?
notification比KVO多了發(fā)送通知的一步。兩者都是一對多,但是對象之間直接的交互,notification明顯得多,需要notificationCenter來做為中間交互。而KVO如我們介紹的,設置觀察者->處理屬性變化,至于中間通知這一環(huán),則隱秘多了,只留一句“交由系統(tǒng)通知”,具體的可參照以上實現(xiàn)過程的剖析。notification的優(yōu)點是監(jiān)聽不局限于屬性的變化,還可以對多種多樣的狀態(tài)變化進行監(jiān)聽,監(jiān)聽范圍廣,例如鍵盤、前后臺等系統(tǒng)通知的使用也更顯靈活方便[6,7]。
5.KVO實現(xiàn)過程中的注意事項
iOS 10以下會有這些情況,iOS11不會出現(xiàn)這些情況,但是為了代碼的嚴謹性,以及以防出現(xiàn)無法預知的錯誤,還是避開這些比較好。
1、添加觀察者次數(shù)與remove次數(shù)不匹配導致程序崩潰
連續(xù)對同一屬性添加觀察者是可以的,但是也要保證在移除觀察者的時候也要移除對應次,不然可能會引發(fā)崩潰(iOS11以上不會崩潰)。
當對同一個keypath進行兩次removeObserver時會導致程序crash,這種情況常常出現(xiàn)在父類有一個kvo,父類在dealloc中remove了一次,子類又remove了一次的情況下。不要以為這種情況很少出現(xiàn)!當你封裝framework開源給別人用或者多人協(xié)作開發(fā)時是有可能出現(xiàn)的,而且這種crash很難發(fā)現(xiàn)。不知道你發(fā)現(xiàn)沒,目前的代碼中context字段都是nil,那能否利用該字段來標識出到底kvo是superClass注冊的,還是self注冊的?我們可以分別在父類以及本類中定義各自的context字符串,比如在本類中定義context為@"ThisIsMyKVOContextNotSuper";然后在dealloc中remove observer時指定移除的自身添加的observer。這樣iOS就能知道移除的是自己的kvo,而不是父類中的kvo,避免二次remove造成crash[8]。
2、移除不存在的觀察者(iOS11以上不會崩潰)
當某個對象并沒有添加觀察者時,卻執(zhí)行了移除觀察者的操作,也會導致程序崩潰,此處不附相關(guān)代碼。
3、被觀察者銷毀時還存在觀察者(iOS11以上不會崩潰)
這種情況常出現(xiàn)在復雜邏輯下,觀察者先于被觀察者銷毀[9]
4、KVO 行為是同步的,并且發(fā)生與所觀察的值發(fā)生變化的同樣的線程上。沒有隊列或者 Run-loop 的處理。手動或者自動調(diào)用 -didChange… 會觸發(fā) KVO 通知。
所以,當我們試圖從其他線程改變屬性值的時候我們應當十分小心,除非能確定所有的觀察者都用線程安全的方法處理 KVO 通知。通常來說,我們不推薦把 KVO 和多線程混起來。如果我們要用多個隊列和線程,我們不應該在它們互相之間用 KVO[10]。
請輸入評論內(nèi)容...
請輸入評論/評論長度6~500個字
最新活動更多
-
10月31日立即下載>> 【限時免費下載】TE暖通空調(diào)系統(tǒng)高效可靠的組件解決方案
-
即日-11.13立即報名>>> 【在線會議】多物理場仿真助跑新能源汽車
-
11月28日立即報名>>> 2024工程師系列—工業(yè)電子技術(shù)在線會議
-
12月19日立即報名>> 【線下會議】OFweek 2024(第九屆)物聯(lián)網(wǎng)產(chǎn)業(yè)大會
-
即日-12.26火熱報名中>> OFweek2024中國智造CIO在線峰會
-
即日-2025.8.1立即下載>> 《2024智能制造產(chǎn)業(yè)高端化、智能化、綠色化發(fā)展藍皮書》
推薦專題
- 高級軟件工程師 廣東省/深圳市
- 自動化高級工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術(shù)專家 廣東省/江門市
- 封裝工程師 北京市/海淀區(qū)
- 結(jié)構(gòu)工程師 廣東省/深圳市