訂閱
糾錯(cuò)
加入自媒體

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

2019-09-26 16:23
EAWorld
關(guān)注

引言:

無(wú)論用哪種語(yǔ)言進(jìn)行軟件開(kāi)發(fā),我們都會(huì)接觸到設(shè)計(jì)模式,個(gè)人認(rèn)為設(shè)計(jì)模式存在的意義在于:在某些需求下,采用適合的設(shè)計(jì)模式,使代碼結(jié)構(gòu)合理,從而提高代碼的可讀性、可擴(kuò)展性、可移植性,此文將要討論的是OC開(kāi)發(fā)中的一種常用模式之一:觀察者模式之KVO。

KVO俗稱鍵值觀察(key-value observe),鍵值觀察是當(dāng)被觀察的對(duì)象屬性發(fā)生改變時(shí),會(huì)通知到觀察對(duì)象的一種機(jī)制。

目錄:

1、KVO的作用

2、KVO的使用方法

3、KVO的實(shí)現(xiàn)原理

4、KVO與KVC、代理、通知的區(qū)別

5、KVO實(shí)現(xiàn)過(guò)程中的注意事項(xiàng)

無(wú)論用哪種語(yǔ)言進(jìn)行軟件開(kāi)發(fā),我們都會(huì)接觸到設(shè)計(jì)模式,個(gè)人認(rèn)為設(shè)計(jì)模式存在的意義在于:在某些需求下,采用適合的設(shè)計(jì)模式,使代碼結(jié)構(gòu)合理,從而提高代碼的可讀性、可擴(kuò)展性、可移植性,此文將要討論的是iOS開(kāi)發(fā)中的一種常用模式之一:觀察者模式之KVO。我們先看下官方文檔給的KVO介紹:

翻譯過(guò)來(lái)就是:KVO是運(yùn)用isa混寫技術(shù)實(shí)現(xiàn)自動(dòng)觀察鍵值的。isa指針是指向?qū)ο蟮念悾举|(zhì)上是指向類中的方法實(shí)現(xiàn)。當(dāng)一個(gè)對(duì)象注冊(cè)觀察者時(shí),這個(gè)對(duì)象的isa指針被修改指向一個(gè)中間類。永遠(yuǎn)不要用isa來(lái)判斷一個(gè)類的繼承關(guān)系,而是應(yīng)該用class方法來(lái)判斷類的實(shí)例。

KVO俗稱鍵值觀察(key-value observe),鍵值觀察是當(dāng)被觀察的對(duì)象屬性發(fā)生改變時(shí),會(huì)通知到觀察對(duì)象的一種機(jī)制。

1.KVO的作用

1、監(jiān)聽(tīng)?zhēng)в袪顟B(tài)的基礎(chǔ)控件,如開(kāi)關(guān)、按鈕等;

2、監(jiān)聽(tīng)字符串的改變,當(dāng)監(jiān)聽(tīng)的字符串改變時(shí),來(lái)做一些自定義的操作;

3、當(dāng)數(shù)據(jù)模型的數(shù)據(jù)發(fā)生改變時(shí),視圖組件能動(dòng)態(tài)的更新,及時(shí)顯示數(shù)據(jù)模型更新后的數(shù)據(jù),比如tableview中數(shù)據(jù)發(fā)生變化進(jìn)行刷新列表操作,監(jiān)聽(tīng) scrollView的contentOffset屬性監(jiān)聽(tīng)頁(yè)面的滑動(dòng).

2.KVO的使用方法

KVO的使用可分為自動(dòng)監(jiān)聽(tīng)和手動(dòng)監(jiān)聽(tīng)。

1.自動(dòng)監(jiān)聽(tīng)

1.1自動(dòng)監(jiān)聽(tīng)操作步驟:

(1)添加觀察者

(2)在觀察者中添加觀察鍵值方法

(3)在dealloc中移除監(jiān)聽(tīng)

1.2示例代碼:

創(chuàng)建兩個(gè)類ModelA和ModelB,兩個(gè)類中都添加屬性“des”,在控制器中,將B添加為A的觀察者。代碼如下:

ModelA中代碼:

ModelB中代碼:

控制器中代碼:

控制器中添加觀察者的方法調(diào)用的是如下的類方法:

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

各個(gè)參數(shù)說(shuō)明:

@param observer 被監(jiān)聽(tīng)的對(duì)象

@param keyPath 被監(jiān)聽(tīng)對(duì)象的屬性名,不可為空,為空崩潰

@param options 有4種

(1)NSKeyValueObservingOptionNew 把更改之前的值提供給處理方法

(2)NSKeyValueObservingOptionOld 把更改之后的值提供給處理方法

(3)NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法,一旦注冊(cè),立馬就會(huì)調(diào)用一次。通常它會(huì)帶有新值,而不會(huì)帶有舊值。

(4)NSKeyValueObservingOptionPrior 分2次調(diào)用。在值改變之前和值改變之后

@param context 上下文

上述示例代碼的運(yùn)行結(jié)果如下所示:

2.手動(dòng)監(jiān)聽(tīng)

意思就是說(shuō):當(dāng)某些需要控制監(jiān)聽(tīng)過(guò)程的場(chǎng)景下,就需要手動(dòng)監(jiān)聽(tīng),比如:為了盡量減少不必要的觸發(fā)通知操作,或者當(dāng)多個(gè)更改同時(shí)具備的時(shí)候才調(diào)用屬性改變的監(jiān)聽(tīng)方法。

實(shí)現(xiàn)手動(dòng)監(jiān)聽(tīng)的要點(diǎn)主要包括這幾部分:

a.重寫

(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key

b.在set方法中在賦值的前后分別調(diào)用

willChangeValueForKey和didChangeValueForKey

2.1實(shí)現(xiàn)部分屬性的手動(dòng)監(jiān)聽(tīng)

在animal.h中添加兩個(gè)屬性age和name,在animal.m中關(guān)閉age的自動(dòng)監(jiān)聽(tīng)功能,其它屬性依然可以自動(dòng)監(jiān)聽(tīng),在控制其中實(shí)現(xiàn)添加按鈕點(diǎn)擊按鈕的時(shí)候改變age的值,并觸發(fā)監(jiān)聽(tīng)方法,代碼如下:

animal類:

要實(shí)現(xiàn)類方法 automaticallyNotifiesObserversForKey,并在其中設(shè)置對(duì)特定的 key 不自動(dòng)發(fā)送通知(返回 NO 即可)。這里要注意,對(duì)其它非手動(dòng)實(shí)現(xiàn)的 key,要轉(zhuǎn)交給 super 來(lái)處理[1,2,3]。

控制器:

當(dāng)不點(diǎn)擊按鈕的時(shí)候,打印結(jié)果只打印了name屬性的值:

當(dāng)點(diǎn)擊按鈕之后,會(huì)手動(dòng)觸發(fā)監(jiān)聽(tīng),打印結(jié)果如下:

2.2所有屬性都手動(dòng)監(jiān)聽(tīng)(禁止自動(dòng)監(jiān)聽(tīng))

如果需要禁用該類KVO的話直接automaticallyNotifiesObserversForKey返回NO。

將animal.m中的類方法修改之后:

運(yùn)行之后不點(diǎn)擊按鈕的話,age和name屬性都不會(huì)自動(dòng)調(diào)用監(jiān)聽(tīng)方法:

點(diǎn)擊了按鈕之后,只有實(shí)現(xiàn)了手動(dòng)監(jiān)聽(tīng)的age屬性調(diào)用了監(jiān)聽(tīng)方法:

3.KVO的實(shí)現(xiàn)原理

當(dāng)某一個(gè)類的實(shí)例第一次使用KVO的時(shí)候,系統(tǒng)就會(huì)在運(yùn)行期間動(dòng)態(tài)的創(chuàng)建該類的一個(gè)派生類,該類的命名規(guī)則一般是以NSKVONotifying為前綴,以原本的類名為后綴。并且將原型的對(duì)象的isa指針指向該派生類。同時(shí)在派生類中重載了使用KVO的屬性的setter方法,在重載的setter方法中實(shí)現(xiàn)真正的通知機(jī)制,正如前面我們手動(dòng)實(shí)現(xiàn)KVO一樣。這么做是基于設(shè)置屬性會(huì)調(diào)用setter方法,而通過(guò)重寫就獲得了 KVO 需要的通知機(jī)制。當(dāng)然前提是要通過(guò)遵循 KVO 的屬性設(shè)置方式來(lái)變更屬性值,如果僅是直接修改屬性對(duì)應(yīng)的成員變量,是無(wú)法實(shí)現(xiàn) KVO 的[4,5]。

4.KVO與KVC、代理、通知的區(qū)別

1.與KVC的不同?

KVC,即是指 NSKeyValueCoding,一個(gè)非正式的 Protocol,提供一種機(jī)制來(lái)間接訪問(wèn)對(duì)象的屬性,而不是通過(guò)調(diào)用Setter、Getter方法等 顯式的存取方式去訪問(wèn)。KVO 就是基于 KVC 實(shí)現(xiàn)的關(guān)鍵技術(shù)之一。

KVO,即Key-Value Observing,它提供一種機(jī)制,當(dāng)指定的對(duì)象的屬性被修改后,對(duì)象就會(huì)接受到通知。

2.與delegate的不同?

和delegate一樣,KVO和NSNotification的作用都是類與類之間的通信。但是與delegate不同的是:這兩個(gè)都是負(fù)責(zé)發(fā)送接收通知,剩下的事情由系統(tǒng)處理,所以不用返回值;而delegate 則需要通信的對(duì)象通過(guò)變量(代理)聯(lián)系;delegate只是一對(duì)一,而這兩個(gè)可以一對(duì)多。delegate是非常嚴(yán)格的語(yǔ)法,需要定義很多代碼。

3.和notification的區(qū)別?

notification比KVO多了發(fā)送通知的一步。兩者都是一對(duì)多,但是對(duì)象之間直接的交互,notification明顯得多,需要notificationCenter來(lái)做為中間交互。而KVO如我們介紹的,設(shè)置觀察者->處理屬性變化,至于中間通知這一環(huán),則隱秘多了,只留一句“交由系統(tǒng)通知”,具體的可參照以上實(shí)現(xiàn)過(guò)程的剖析。notification的優(yōu)點(diǎn)是監(jiān)聽(tīng)不局限于屬性的變化,還可以對(duì)多種多樣的狀態(tài)變化進(jìn)行監(jiān)聽(tīng),監(jiān)聽(tīng)范圍廣,例如鍵盤、前后臺(tái)等系統(tǒng)通知的使用也更顯靈活方便[6,7]。

5.KVO實(shí)現(xiàn)過(guò)程中的注意事項(xiàng)

iOS 10以下會(huì)有這些情況,iOS11不會(huì)出現(xiàn)這些情況,但是為了代碼的嚴(yán)謹(jǐn)性,以及以防出現(xiàn)無(wú)法預(yù)知的錯(cuò)誤,還是避開(kāi)這些比較好。

1、添加觀察者次數(shù)與remove次數(shù)不匹配導(dǎo)致程序崩潰

連續(xù)對(duì)同一屬性添加觀察者是可以的,但是也要保證在移除觀察者的時(shí)候也要移除對(duì)應(yīng)次,不然可能會(huì)引發(fā)崩潰(iOS11以上不會(huì)崩潰)。

當(dāng)對(duì)同一個(gè)keypath進(jìn)行兩次removeObserver時(shí)會(huì)導(dǎo)致程序crash,這種情況常常出現(xiàn)在父類有一個(gè)kvo,父類在dealloc中remove了一次,子類又remove了一次的情況下。不要以為這種情況很少出現(xiàn)!當(dāng)你封裝framework開(kāi)源給別人用或者多人協(xié)作開(kāi)發(fā)時(shí)是有可能出現(xiàn)的,而且這種crash很難發(fā)現(xiàn)。不知道你發(fā)現(xiàn)沒(méi),目前的代碼中context字段都是nil,那能否利用該字段來(lái)標(biāo)識(shí)出到底kvo是superClass注冊(cè)的,還是self注冊(cè)的?我們可以分別在父類以及本類中定義各自的context字符串,比如在本類中定義context為@"ThisIsMyKVOContextNotSuper";然后在dealloc中remove observer時(shí)指定移除的自身添加的observer。這樣iOS就能知道移除的是自己的kvo,而不是父類中的kvo,避免二次remove造成crash[8]。

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

當(dāng)某個(gè)對(duì)象并沒(méi)有添加觀察者時(shí),卻執(zhí)行了移除觀察者的操作,也會(huì)導(dǎo)致程序崩潰,此處不附相關(guān)代碼。

3、被觀察者銷毀時(shí)還存在觀察者(iOS11以上不會(huì)崩潰)

這種情況常出現(xiàn)在復(fù)雜邏輯下,觀察者先于被觀察者銷毀[9]

4、KVO 行為是同步的,并且發(fā)生與所觀察的值發(fā)生變化的同樣的線程上。沒(méi)有隊(duì)列或者 Run-loop 的處理。手動(dòng)或者自動(dòng)調(diào)用 -didChange… 會(huì)觸發(fā) KVO 通知。

所以,當(dāng)我們?cè)噲D從其他線程改變屬性值的時(shí)候我們應(yīng)當(dāng)十分小心,除非能確定所有的觀察者都用線程安全的方法處理 KVO 通知。通常來(lái)說(shuō),我們不推薦把 KVO 和多線程混起來(lái)。如果我們要用多個(gè)隊(duì)列和線程,我們不應(yīng)該在它們互相之間用 KVO[10]。

聲明: 本文由入駐維科號(hào)的作者撰寫,觀點(diǎn)僅代表作者本人,不代表OFweek立場(chǎng)。如有侵權(quán)或其他問(wèn)題,請(qǐng)聯(lián)系舉報(bào)。

發(fā)表評(píng)論

0條評(píng)論,0人參與

請(qǐng)輸入評(píng)論內(nèi)容...

請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字

您提交的評(píng)論過(guò)于頻繁,請(qǐng)輸入驗(yàn)證碼繼續(xù)

  • 看不清,點(diǎn)擊換一張  刷新

暫無(wú)評(píng)論

暫無(wú)評(píng)論

人工智能 獵頭職位 更多
掃碼關(guān)注公眾號(hào)
OFweek人工智能網(wǎng)
獲取更多精彩內(nèi)容
文章糾錯(cuò)
x
*文字標(biāo)題:
*糾錯(cuò)內(nèi)容:
聯(lián)系郵箱:
*驗(yàn) 證 碼:

粵公網(wǎng)安備 44030502002758號(hào)