kvo 實(shí)踐使用總結(jié)

上半年有段時(shí)間做了一個(gè)項(xiàng)目,項(xiàng)目中聊天界面用到了音頻播放,涉及到進(jìn)度條,當(dāng)時(shí)做android時(shí)候處理的不太好,由于item復(fù)用導(dǎo)致進(jìn)度條會(huì)被多個(gè)信息實(shí)體引用控制,雖然最后繞啊繞,也解決了,但是費(fèi)了老大勁。所以做ios時(shí)候,就使用了kvo以盡量實(shí)現(xiàn)解耦。

使用kvo過(guò)程中,也是經(jīng)歷了一些坑。

本篇文章,學(xué)完第一二節(jié),結(jié)合自己實(shí)踐就能使用了。后面的章節(jié),可以作為自己的拔高,嘿嘿

備注:寫(xiě)該篇文章也借鑒參考了許多大牛的文章,結(jié)合自己的實(shí)踐,總結(jié)了一下。大牛勿噴。嘿嘿

一、KVO是什么?

  • KVO 是 Objective-C 對(duì)觀察者設(shè)計(jì)模式的一種實(shí)現(xiàn)?!玖硗庖环N是:通知機(jī)制(notification)】;
  • KVO提供一種機(jī)制,指定一個(gè)被觀察對(duì)象(例如A類),當(dāng)對(duì)象某個(gè)屬性(例如A中的字符串name)發(fā)生更改時(shí),監(jiān)聽(tīng)對(duì)象會(huì)獲得通知,并作出相應(yīng)處理;【且不需要給被觀察的對(duì)象添加任何額外代碼,就能使用KVO機(jī)制】
    在MVC設(shè)計(jì)架構(gòu)下的項(xiàng)目,KVO機(jī)制很適合實(shí)現(xiàn)mode模型和view視圖之間的通訊。

例如:代碼中,在模型類A創(chuàng)建屬性數(shù)據(jù),在控制器中創(chuàng)建觀察者,一旦屬性數(shù)據(jù)發(fā)生改變就收到觀察者收到通知,通過(guò)KVO再在控制器使用回調(diào)方法處理實(shí)現(xiàn)視圖B的更新;

KVC與KVO的不同

KVC(鍵值編碼),即Key-Value Coding,一個(gè)非正式的Protocol,使用字符串(鍵)訪問(wèn)一個(gè)對(duì)象實(shí)例變量的機(jī)制。而不是通過(guò)調(diào)用Setter、Getter方法等顯式的存取方式去訪問(wèn)。
KVO(鍵值監(jiān)聽(tīng)),即Key-Value Observing,它提供一種機(jī)制,當(dāng)指定的對(duì)象的屬性被修改后,對(duì)象就會(huì)接受到通知,前提是執(zhí)行了setter方法、或者使用了KVC賦值。

和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)范圍廣,例如鍵盤(pán)、前后臺(tái)等系統(tǒng)通知的使用也更顯靈活方便。

與delegate的不同

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

二、kvo簡(jiǎn)單使用

1:注冊(cè)觀察者,實(shí)施監(jiān)聽(tīng);

  • 被觀察對(duì)象必須能支持kvc機(jī)制——所有NSObject的子類都支持這個(gè)機(jī)制
  • 必須用 被觀察對(duì)象 的 addObserver:forKeyPath:options:context: 方法注冊(cè)觀察者
  • 觀察者 必須實(shí)現(xiàn) observeValueForKeyPath:ofObject:change:context: 方法
[self.model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

- observer 指觀察者

- keyPath 表示被觀察者的屬性
 
- options 決定了提供給觀察者change字典中的具體信息有哪些。 【見(jiàn)options解析】

- context 這個(gè)參數(shù)可以是一個(gè) C指針,也可以是一個(gè) 對(duì)象引用,它可以作為這個(gè)context的唯一標(biāo)識(shí),也可以提供一些數(shù)據(jù)給觀察者。因?yàn)槟銈鬟M(jìn)去是啥,回調(diào)時(shí)候還是回傳的還是啥

:options解析

typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
    表示監(jiān)聽(tīng)對(duì)象的新值(變化后的值),change字典中會(huì)包含有該key的鍵值對(duì),通過(guò)該key,就可以取到屬性變化后的值
    NSKeyValueObservingOptionNew = 0x01,
    
    表示監(jiān)聽(tīng)對(duì)象的舊值(變化前的值),change字典中會(huì)包含有該key的鍵值對(duì),通過(guò)該key,就可以取到屬性變化前的值
    NSKeyValueObservingOptionOld = 0x02,
    
    在注冊(cè)觀察者的方法return的時(shí)候就會(huì)發(fā)出一次通知。比如:在viewDidLoad中注冊(cè)的監(jiān)聽(tīng),那viewDidLoad方法運(yùn)行完,通知就發(fā)出去了
    NSKeyValueObservingOptionInitial NS_ENUM_AVAILABLE(10_5, 2_0) = 0x04,
    
    會(huì)在值發(fā)生改變前發(fā)出一次通知,當(dāng)然改變后的通知依舊還會(huì)發(fā)出,也就是每次change都會(huì)有兩個(gè)通知
    NSKeyValueObservingOptionPrior NS_ENUM_AVAILABLE(10_5, 2_0) = 0x08
};
  • 注冊(cè)監(jiān)聽(tīng),options入?yún)⑹莻€(gè)枚舉,該入?yún)⒏O(jiān)聽(tīng)回調(diào)中的change呼應(yīng)。。并且,以上options入?yún)r(shí)候是可以用 | 或運(yùn)算進(jìn)行多選的。
    • 例如:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld。。那在change字典中就會(huì)包含屬性變化前后的值。。
    • 注意:通過(guò)多options監(jiān)聽(tīng)屬性的時(shí)候,例如上,并不是回到一次老值,再回調(diào)一次新值,,而是新老值都是在change字典中的。。

2:監(jiān)聽(tīng)回調(diào)

  • 觀察者實(shí)現(xiàn)方法都一樣:observeValueForKeyPath:ofObject:change:context: 就這一個(gè)方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    - keyPath:你所觀察對(duì)象的屬性
    - object:你所觀察的對(duì)象
    - change:你所觀察對(duì)象屬性值的變化
}

:change解析

NSKeyValueChangeKey枚舉

監(jiān)聽(tīng)回調(diào)中,通過(guò)key獲取監(jiān)聽(tīng)屬性的變化值。如下枚舉:

FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeKindKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNewKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeOldKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeIndexesKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNotificationIsPriorKey
  • NSKeyValueChangeKindKey

    • 這個(gè)key包含的value是一個(gè) NSNumber 里面是一個(gè) int
      (有點(diǎn)繞:value是[NSNumber numberWithInt:xxx]),
      與之對(duì)應(yīng)的是 NSKeyValueChange 的枚舉
  • NSKeyValueChangeNewKey

    • 跟options中的對(duì)應(yīng)
  • NSKeyValueChangeOldKey

    • 跟options中的對(duì)應(yīng)
  • NSKeyValueChangeIndexesKey

    • 當(dāng) NSKeyValueChangeKindKey 的結(jié)果是 NSKeyValueChangeInsertion,
      NSKeyValueChangeRemoval 或 NSKeyValueChangeReplacement 的時(shí)候,
      這個(gè)key的value是一個(gè)NSIndexSet,包含了發(fā)生insert,remove,replace的對(duì)象的索引集合
  • NSKeyValueChangeNotificationIsPriorKey

    • 這個(gè)key包含了一個(gè) NSNumber,里面是一個(gè)布爾值,如果在注冊(cè)時(shí) options 中有
      NSKeyValueObservingOptionPrior,那么在前一個(gè)通知中的 change 中就會(huì)
      有這個(gè)key的value, 我們可以這樣來(lái)判斷是不是在改變前的通知[change[NSKeyValueChangeNotificationIsPriorKey] boolValue] ==
      YES;】

    說(shuō)明: change是個(gè)字典,ios中dic獲取值通常用valueForKey或objectFroKey,,以上方式也可,dic[@"key"]..方便快捷。大家可以了解一下,不失為一種方式。。

NSKeyValueChange枚舉

typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4,
};
  • 當(dāng) change[NSKeyValueChangeKindKey] 是 NSKeyValueChangeSetting 的時(shí)候,說(shuō)明被觀察屬性的setter方法被調(diào)用了
    • Insert, Remove, Replace:被觀察屬性是集合類型,且對(duì)它進(jìn)行了 insert,remove,replace 操作的時(shí)候會(huì)返回這三種Key

:context解析

context作用一般都被忽略了。主要還是平常使用kvo都是簡(jiǎn)單的訂閱-響應(yīng)-移除。很少涉及到 多訂閱-響應(yīng)-多移除 或 多訂閱-多響應(yīng)-多移除。。。在以下的 三、kvo注意事項(xiàng)中有詳解

3:移除觀察者

你可以通過(guò) removeObserver:forKeyPath: 或 removeObserver:forKeyPath:context: 方法來(lái)移除一個(gè)觀察。

注意:如果你的 context 是一個(gè) 對(duì)象,你必須在移除觀察之前持有它的強(qiáng)引用。當(dāng)移除了觀察后,觀察者對(duì)象再也不會(huì)受到這個(gè) keyPath 的通知。

三、kvo使用注意事項(xiàng)

:注冊(cè)監(jiān)聽(tīng)

  • 多次添加相同的監(jiān)聽(tīng)
    • 也就是添加過(guò)的監(jiān)聽(tīng),都要挨個(gè)移除。。所以這一點(diǎn),在cell中使用時(shí)候要特別注意。因?yàn)閏ell多次運(yùn)行,監(jiān)聽(tīng)可能就是多次添加
    • 效果如下圖
observation.png

:響應(yīng)

  • kvo觸發(fā)是嚴(yán)格依賴kvc機(jī)制的。簡(jiǎn)單來(lái)說(shuō)就是觸發(fā)kvo必須是kvc方式給屬性賦值。。

    • 反例:_name = @"qkn"..這種是不會(huì)觸發(fā)響應(yīng)的。。。
    • 因?yàn)闆](méi)有調(diào)用屬性的setter方法,所以也就不會(huì)觸發(fā)notify,,kvo原理深入分析中有講kvo的實(shí)現(xiàn)原理
  • 由于監(jiān)聽(tīng)回調(diào)是一個(gè)函數(shù),可能有多個(gè)監(jiān)聽(tīng),所以,比較好的邏輯是,通過(guò)object和keypath過(guò)濾出來(lái)你要監(jiān)聽(tīng)的對(duì)象-屬性

  • KVO嚴(yán)重依賴string,換句話說(shuō),KVO中的keyPath必須是NSString這個(gè)事實(shí)使得編譯器沒(méi)辦法在編譯階段將錯(cuò)誤的keyPath給找出來(lái);譬如很容易將「contentSize」寫(xiě)成「contentsize」;

    • 方案一:使用NSStringFromSelector(SEL aSelector)方法,即改@"contentSize"為NSStringFromSelector(@selector(contentSize))
    • 方案二:#define varName(var) [NSString stringWithFormat:@"%s",#var]。。使用:varName(屬性名)。。
  • 對(duì)于Objective-C,很多時(shí)候runtime系統(tǒng)都會(huì)自動(dòng)幫助處理superclass的方法。但對(duì)于KVO不會(huì)這樣,所以為了保證父類(父類可能也會(huì)自己observe處理嘛)的observe事務(wù)也能被處理。所以要注意:在過(guò)濾到自己監(jiān)聽(tīng)的屬性后,要有個(gè)else分支,去處理:[superobserveValueForKeyPath:keyPath ofObject:object change:change context:context];

  • 針對(duì)上面那個(gè)問(wèn)題,如果業(yè)務(wù)比較復(fù)雜,多類監(jiān)聽(tīng)了同一對(duì)象或爺父子孫類監(jiān)聽(tīng)了同一對(duì)象。怎么整?

    • 原則:誰(shuí)的事兒誰(shuí)負(fù)責(zé)
    • 所以,用到context,類注冊(cè)監(jiān)聽(tīng)時(shí)候,傳一個(gè)獨(dú)一無(wú)二的值。建議把自己的類名傳進(jìn)去。在回調(diào)監(jiān)聽(tīng)時(shí)候,就可以通過(guò)context進(jìn)行檢驗(yàn)過(guò)濾了
    • 但是如果一個(gè)類監(jiān)聽(tīng)一個(gè)對(duì)象的多個(gè)屬性呢?傳的context也不夠用了。還有object和keyPath呢。。這三個(gè)參數(shù)已經(jīng)可以確定獨(dú)一無(wú)二的監(jiān)聽(tīng)

:移除監(jiān)聽(tīng)

  • 有兩個(gè)方法:
    • 建議用上面的。注冊(cè),響應(yīng),取消。方法保持一樣
-(void)removeObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath context:(void *)context;

-(void)removeObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath;
  • 我們一般會(huì)在dealloc中進(jìn)行removeObserver操作(這也是Apple所推薦的)

  • 取消訂閱可能會(huì)crash:移除一個(gè)不存在的監(jiān)聽(tīng)

  • 多次remove相同的監(jiān)聽(tīng)會(huì)導(dǎo)致crash

    • 解決方案同上
    • 另外:同一個(gè)對(duì)象同一屬性的多次監(jiān)聽(tīng)(添加順序:123),默認(rèn)移除時(shí)候,也是要多次移除(移除順序321)。
    • 當(dāng)然也可以指定context。例如:注冊(cè)監(jiān)聽(tīng)時(shí)候context入?yún)锧“1”,那么移除時(shí)候,就可以指定context為@“1”。并別這不是比對(duì)指針,而是值。
context.png
所以,如果使用中用到了context傳的是字符串,索性也就把context值提出來(lái)作為公共的獨(dú)一無(wú)二的,避免像keyPath入?yún)⒁粯?,誤寫(xiě)了。但是如果是c指針,或?qū)ο笠镁土碚f(shuō)了
  • 如果某對(duì)象被釋放時(shí)候,還有對(duì)象在監(jiān)聽(tīng)它,也會(huì)報(bào)錯(cuò)
reason: 'An instance 0x7fed3ef6e170 of class SecVC was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x600000038300> (
<NSKeyValueObservance 0x60000004ff60: Observer: 0x7fed3ef6e170, Key path: changeColor, Options: <New: NO, Old: NO, Prior: NO> Context: 0x0, Property: 0x60000005b720>

解決方案:

Facebook開(kāi)源一個(gè)庫(kù),KVOController。移除監(jiān)聽(tīng)不用再自己管理

四、kvo實(shí)現(xiàn)原理

原理

雖然ios不開(kāi)源,官方api說(shuō)的也很有限,但是kvo原理已經(jīng)被大家通過(guò)“黑科技”摸透了

  • KVO在Apple中的API文檔如下:
Automatic key-value observing is implemented using a technique called isa-swizzling… 
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, 
pointing to an intermediate class rather than at the true class …

簡(jiǎn)單翻譯:在我們對(duì)某個(gè)對(duì)象完成監(jiān)聽(tīng)的注冊(cè)后,編譯器會(huì)修改監(jiān)聽(tīng)對(duì)象(下文中原理驗(yàn)證中的test對(duì)象)的isa指針,讓這個(gè)指針指向一個(gè)新生成的中間類。從某個(gè)意義上來(lái)說(shuō),這是一場(chǎng)騙局。

這里要說(shuō)明的是isa這個(gè)指針,isa是一個(gè)Class類型的指針,對(duì)象的首地址一般是isa變量,同時(shí)isa又保存了對(duì)象的類對(duì)象的首地址。我們通過(guò)object_getClass方法來(lái)獲取這個(gè)對(duì)象的元類,即是對(duì)象的類對(duì)象的類型(正常來(lái)說(shuō),class方法內(nèi)部的實(shí)現(xiàn)就是獲取這個(gè)isa保存的對(duì)象的類型,在kvo的實(shí)現(xiàn)中蘋(píng)果對(duì)被監(jiān)聽(tīng)對(duì)象的class方法進(jìn)行了重寫(xiě)隱藏了實(shí)現(xiàn))。class方法是獲得對(duì)象的類型,雖然這兩個(gè)返回的結(jié)果是一樣的,但是兩個(gè)方法在本質(zhì)上得到的結(jié)果不是同一個(gè)東西
在oc中,規(guī)定了只要擁有isa指針的變量,通通都屬于對(duì)象。上面的objc_object表示的是NSObject這個(gè)類的結(jié)構(gòu)體表示,因此oc不允許出現(xiàn)非NSObject子類的對(duì)象(block是一個(gè)特殊的例外)*
當(dāng)然了,蘋(píng)果并不想講述更多的實(shí)現(xiàn)細(xì)節(jié),但是我們可以通過(guò)運(yùn)行時(shí)機(jī)制來(lái)完成一些有趣的調(diào)試。

  • 原理簡(jiǎn)析:引自滴滴構(gòu)架師 sunnyxx 的一篇文章 objc kvo簡(jiǎn)單探索
    • 當(dāng)一個(gè)object有觀察者時(shí),動(dòng)態(tài)創(chuàng)建這個(gè)object的類的子類
    • 對(duì)于每個(gè)被觀察的property,重寫(xiě)其set方法
    • 在重寫(xiě)的set方法中調(diào)用- willChangeValueForKey:和- didChangeValueForKey:通知觀察者
    • 當(dāng)一個(gè)property沒(méi)有觀察者時(shí),刪除重寫(xiě)的方法
    • 當(dāng)沒(méi)有observer觀察任何一個(gè)property時(shí),刪除動(dòng)態(tài)創(chuàng)建的子類

原理驗(yàn)證

(lldb)po xxx.class -> 對(duì)象的類型

(lldb)po object-getClass(xxx) -> 對(duì)象的類對(duì)象的類型

1:聲明對(duì)象

debug1.png

結(jié)果:

result1.png

2:注冊(cè)監(jiān)聽(tīng)

debug2.png

結(jié)果:

result2.png

3:移除監(jiān)聽(tīng)

debug3.png

結(jié)果:

result3.png

上面的結(jié)果說(shuō)明,在test對(duì)象被觀察時(shí),framework使用runtime動(dòng)態(tài)創(chuàng)建了一個(gè)Sark類的子類NSKVONotifying_Test
而且為了隱藏這個(gè)行為,NSKVONotifying_Sark重寫(xiě)了- class方法返回之前的類,就好像什么也沒(méi)發(fā)生過(guò)一樣
但是使用object_getClass()時(shí)就暴露了,因?yàn)檫@個(gè)方法返回的是這個(gè)對(duì)象的isa指針,這個(gè)指針指向的一定是個(gè)這個(gè)對(duì)象的類對(duì)象

拓展:類 動(dòng)態(tài)創(chuàng)建后,觀察一下這個(gè)動(dòng)態(tài)中間類實(shí)現(xiàn)的方法

ios 代碼庫(kù)。。大家可以積累下

NSObject+DLIntrospection :: 它封裝了打印一個(gè)類的方法、屬性、協(xié)議等常用調(diào)試方法,一目了然。

po [object_getClass(test) instanceMethods] -> 打印類內(nèi)部實(shí)現(xiàn)方法

debug2.png
kvomethods.png

說(shuō)明:

    • setxxx 最主要的重寫(xiě)方法,set值時(shí)調(diào)用通知函數(shù), 接下來(lái)會(huì)深入分析重寫(xiě)setter方法
    • class 隱藏自己必備啊,返回原來(lái)類的class。。有了這個(gè)方法,你就不會(huì)知道,系統(tǒng)已經(jīng)創(chuàng)建了中間類NSKVONotifying_xxx
    • dealloc 做清理犯罪現(xiàn)場(chǎng)工作。。既然實(shí)現(xiàn)了一個(gè)類,里面做的操作,產(chǎn)生的垃圾啥的,都要釋放回收
    • _isKVOA 這就是內(nèi)部使用的標(biāo)示了,判斷這個(gè)類有沒(méi)被KVO動(dòng)態(tài)生成子類

原理深入分析

Apple 使用了 isa 混寫(xiě)(isa-swizzling)來(lái)實(shí)現(xiàn) KVO 。當(dāng)觀察對(duì)象A時(shí),KVO機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)新的名為: NSKVONotifying_A的新類,該類繼承自對(duì)象A的本類,且KVO為NSKVONotifying_A重寫(xiě)觀察屬性的setter 方法,setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后,通知所有觀察對(duì)象屬性值的更改情況。

(備注: isa 混寫(xiě)(isa-swizzling)isa:is a kind of ; swizzling:混合,攪合;)

①NSKVONotifying_A類剖析:在這個(gè)過(guò)程,被觀察對(duì)象的 isa 指針從指向原來(lái)的A類,被KVO機(jī)制修改為指向系統(tǒng)新創(chuàng)建的子類 NSKVONotifying_A類,來(lái)實(shí)現(xiàn)當(dāng)前類屬性值改變的監(jiān)聽(tīng);

所以當(dāng)我們從應(yīng)用層面上看來(lái),完全沒(méi)有意識(shí)到有新的類出現(xiàn),這是系統(tǒng)“隱瞞”了對(duì)KVO的底層實(shí)現(xiàn)過(guò)程,讓我們誤以為還是原來(lái)的類。但是此時(shí)如果我們創(chuàng)建一個(gè)新的名為“NSKVONotifying_A”的類(),就會(huì)發(fā)現(xiàn)系統(tǒng)運(yùn)行到注冊(cè)KVO的那段代碼時(shí)程序就崩潰,因?yàn)橄到y(tǒng)在注冊(cè)監(jiān)聽(tīng)的時(shí)候動(dòng)態(tài)創(chuàng)建了名為NSKVONotifying_A的中間類,并指向這個(gè)中間類了。

(isa 指針的作用:每個(gè)對(duì)象都有isa 指針,指向該對(duì)象的類,它告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類是什么。所以對(duì)象注冊(cè)為觀察者時(shí),isa指針指向新子類,那么這個(gè)被觀察的對(duì)象就神奇地變成新子類的對(duì)象(或?qū)嵗┝恕#?因而在該對(duì)象上對(duì) setter 的調(diào)用就會(huì)調(diào)用已重寫(xiě)的 setter,從而激活鍵值通知機(jī)制。

—>我猜,這也是KVO回調(diào)機(jī)制,為什么都俗稱KVO技術(shù)為黑魔法的原因之一吧:內(nèi)部神秘、外觀簡(jiǎn)潔。

②子類setter方法剖析:KVO的鍵值觀察通知依賴于 NSObject 的兩個(gè)方法:willChangeValueForKey:和 didChangevlueForKey:,在存取數(shù)值的前后分別調(diào)用2個(gè)方法:

被觀察屬性發(fā)生改變之前,willChangeValueForKey:被調(diào)用,通知系統(tǒng)該 keyPath 的屬性值即將變更;當(dāng)改變發(fā)生后, didChangeValueForKey: 被調(diào)用,通知系統(tǒng)該 keyPath 的屬性值已經(jīng)變更;之后, observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用。且重寫(xiě)觀察屬性的setter 方法這種繼承方式的注入是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的。

KVO為子類的觀察者屬性重寫(xiě)調(diào)用存取方法的工作原理在代碼中相當(dāng)于:

-(void)setName:(NSString *)newName
{
    [self willChangeValueForKey:@"name"];    //KVO在調(diào)用存取方法之前總調(diào)用
    [super setValue:newName forKey:@"name"]; //調(diào)用父類的存取方法
    [self didChangeValueForKey:@"name"];     //KVO在調(diào)用存取方法之后總調(diào)用
}

系統(tǒng)重寫(xiě)了setter方法:如何獲知呢?

在你監(jiān)聽(tīng)的屬性所在的類中,重寫(xiě)willChangeValueForKey 和 didChangeValueForKey。。在你注冊(cè)完監(jiān)聽(tīng),改變監(jiān)聽(tīng)屬性值的時(shí)候,看能不能走下面的方法就ok了

- (void)willChangeValueForKey:(NSString *)key {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [super willChangeValueForKey:key];
}

- (void)didChangeValueForKey:(NSString *)key {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [super didChangeValueForKey:key];
}

五、kvo進(jìn)階使用

監(jiān)聽(tīng)readOnly屬性

如果在開(kāi)發(fā)中,不幸遇到要對(duì)readonly的屬性進(jìn)行kvo監(jiān)聽(tīng),那就只能求心理陰影面積了。。因?yàn)閞eadonly的屬性沒(méi)有setter方法呀。那怎么去觸發(fā)kvo通知。
注意: 以下方案只針對(duì)自己創(chuàng)建的類的屬性有效,對(duì)于系統(tǒng)的屬性無(wú)效..親測(cè),使用分類,通過(guò)_xxx拿到系統(tǒng)的屬性,還是不行。哈哈。。說(shuō)到底,那還是系統(tǒng)的。豈能讓你亂動(dòng)

方案一:
系統(tǒng)怎么實(shí)現(xiàn)的,咱也怎么實(shí)現(xiàn)。。在給屬性賦值前后,模仿系統(tǒng)的做法,如下:

[self willChangeValueForKey:@"xxx"];

_xxx = xxx;

[self didChangeValueForKey:@"xxx"];

方案二:

[被觀察者 setValue:xxx forKey:@"xxx"];

監(jiān)聽(tīng)數(shù)組變化

iOS默認(rèn)不支持對(duì)數(shù)組的KVO,因?yàn)槠胀ǚ绞奖O(jiān)聽(tīng)的對(duì)象的地址的變化,而數(shù)組地址不變,而是里面的值發(fā)生了改變。
親測(cè):

objectAddress.png
  • 該實(shí)踐并沒(méi)有什么實(shí)際作用,項(xiàng)目中不適宜這么用。除非有特殊需求,這么整也算是一種方案

使用方法:

1:創(chuàng)建繼承object類:一般就是創(chuàng)建一個(gè)模型,里面的屬性是你要監(jiān)聽(tīng)的數(shù)組

2:注冊(cè)監(jiān)聽(tīng):[_object addObserver:forKeyPath:options:context]

3:改變數(shù)組,觸發(fā)監(jiān)聽(tīng):[_object mutableArrayValueForKey:xxx] addObject:xxx]

4:響應(yīng)監(jiān)聽(tīng):observeValueForKeyPath~

注意: 第一步:初始化要監(jiān)聽(tīng)的數(shù)組,必須通過(guò)kvc方式。否則初始化不了。

1: 新建數(shù)組初始化
NSDictionary *dic = [NSDictionary dictionaryWithObject:[NSMutableArray arrayWithCapacity:0] forKey:@"數(shù)組屬性名"];  

self.model = [[model alloc] initWithDic:dic];
        
2: kvc方式,給模型中數(shù)組初始化
-(id)initWithDic:(NSDictionary *)dic  
{  
    self = [super init];  
    if (self) {  
        [self setValuesForKeysWithDictionary:dic];  
    }  

    return self;  
}  

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

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

  • 本文結(jié)構(gòu)如下: Why? (為什么要用KVO) What? (KVO是什么) How? ( KVO怎么用) Mo...
    等開(kāi)會(huì)閱讀 1,736評(píng)論 1 21
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,083評(píng)論 0 9
  • 本文由我們團(tuán)隊(duì)的 糾結(jié)倫 童鞋撰寫(xiě)。 文章結(jié)構(gòu)如下: Why? (為什么要用KVO) What? (KVO是什么...
    知識(shí)小集閱讀 7,487評(píng)論 7 105
  • KVC 什么是 KVC KVC 是 Key-Value-Coding 的簡(jiǎn)稱。 KVC 是一種可以直接通過(guò)字符串的...
    LeeJay閱讀 2,272評(píng)論 6 41
  • 一、概述 KVO,即:Key-Value Observing,它提供一種機(jī)制,當(dāng)指定的對(duì)象的屬性被修改后,則其觀察...
    DeerRun閱讀 10,213評(píng)論 11 33

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