概述
- KVO全稱Key-Value-Observing,也叫鍵值監(jiān)聽(tīng),是一種觀察者設(shè)計(jì)模式.提供了一種機(jī)制,當(dāng)指定的對(duì)象的屬性被修改后,對(duì)象就會(huì)收到一個(gè)通知.也就是說(shuō)每次指定的被觀察的對(duì)象的屬性被修改后,KVO就會(huì)自動(dòng)通知相應(yīng)的觀察者.
- 優(yōu)勢(shì):可以降低兩個(gè)類(業(yè)務(wù)邏輯和視圖控制的類)之間的耦合性.也就是說(shuō)可以很容易的實(shí)現(xiàn)視圖組件和數(shù)據(jù)模型的分離.當(dāng)數(shù)據(jù)模型的屬性值改變之后作為監(jiān)聽(tīng)器的視圖組件就會(huì)被激發(fā)坝冕,激發(fā)時(shí)就會(huì)回調(diào)監(jiān)聽(tīng)器自身.
- 在Objective-C中要實(shí)現(xiàn)KVO則必須實(shí)現(xiàn)NSKeyValueObserving協(xié)議.但不用擔(dān)心,因?yàn)镹SObject已經(jīng)實(shí)現(xiàn)了該協(xié)議,因此幾乎所有的Objective-C對(duì)象都可以使用KVO.
KVO的方法
- 監(jiān)聽(tīng)方法
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
某一個(gè)對(duì)象(受虐狂,喜歡被人監(jiān)視),給自己添加一個(gè)監(jiān)聽(tīng)者(一般都是控制器本身self),讓監(jiān)聽(tīng)者監(jiān)聽(tīng)自身的某一個(gè)屬性. options就是要求監(jiān)聽(tīng)者記錄的信息. context就是要監(jiān)聽(tīng)者給自己添加一個(gè)標(biāo)記,以防止和別的對(duì)象的監(jiān)聽(tīng)混淆. 比如: 有兩個(gè)孩子讓家長(zhǎng)監(jiān)聽(tīng)他們做作業(yè).監(jiān)聽(tīng)者是家長(zhǎng),被監(jiān)聽(tīng)的對(duì)象是兩個(gè)孩子.
參數(shù):-
observer
觀察者,也就是KVO的訂閱者,訂閱者必須實(shí)現(xiàn)協(xié)議方法(下面有). -
keyPath
描述將要觀察的對(duì)象的屬性,也就是被觀察者的屬性. -
options
KVO的屬性配置.-
NSKeyValueObservingOptionNew
change字典包括改變后的值 -
NSKeyValueObservingOptionOld
change字典包括改變前的值 -
NSKeyValueObservingOptionInitial
注冊(cè)后立刻觸發(fā)KVO通知 -
NSKeyValueObservingOptionPrior
值改變前是否也要通知(這個(gè)key決定了是否在改變前改變后通知兩次)
-
-
context
上下文趁尼,這個(gè)會(huì)傳遞到協(xié)議方法中,用來(lái)區(qū)分消息,處理不同的KVO.所以應(yīng)當(dāng)是不同的.
-
- 解除監(jiān)聽(tīng)
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
刪除指定keyPath的監(jiān)聽(tīng)器.
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);
刪除特定上下文標(biāo)記的指定keyPath的監(jiān)聽(tīng)器. - 回調(diào)監(jiān)聽(tīng)
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
參數(shù):-
keyPath
被監(jiān)聽(tīng)的keyPath -
object
被監(jiān)聽(tīng)的修改后的對(duì)象,可以獲取修改的對(duì)象的屬性 -
change
保存信息改變的字典(可能有舊的值瓤逼,新的值等 -
context
上下文
-
使用步驟
- 注冊(cè)KVO監(jiān)聽(tīng).
- 實(shí)現(xiàn)代理方法.
- 移除監(jiān)聽(tīng).在dealloc方法中移除.
KVO使用注意事項(xiàng)
非常重要
- 當(dāng)你在同一個(gè)ViewController中添加多個(gè)KVO的時(shí)候,無(wú)論哪個(gè)KVO都是走
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
回調(diào)方法.所以需要對(duì)想要的監(jiān)聽(tīng)對(duì)象進(jìn)行區(qū)分,以便指定不同的邏輯.
這里是對(duì)_tableView
對(duì)象的contentOffset
屬性監(jiān)聽(tīng).
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) {
[self doSomething];
}
}
- 我們假設(shè)當(dāng)前類(在例子中為UITableViewController)還有父類踢关,并且父類也有自己綁定了一些其他KVO呢伞鲫?我們看到,上述回調(diào)函數(shù)體中只有一個(gè)判斷签舞,如果這個(gè)if不成立秕脓,這次KVO事件的觸發(fā)就會(huì)到此中斷了柒瓣。但事實(shí)上,若當(dāng)前類無(wú)法捕捉到這個(gè)KVO吠架,那很有可能是在他的superClass芙贫,或者super-superClass...中,上述處理砍斷了這個(gè)鏈傍药。合理的處理方式應(yīng)該是這樣的:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) {
[self doSomethingWhenContentOffsetChanges];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; }
}
但是這個(gè)是要自己搞清楚,父類中到底有沒(méi)有注冊(cè)KVO.如果監(jiān)聽(tīng)一個(gè)對(duì)象的兩個(gè)屬性,兩個(gè)屬性的改變時(shí)分開(kāi)執(zhí)行的,就會(huì)觸發(fā)兩次代理方法.如圖:
1.png
- KVO的一個(gè)特性,當(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).解決辦法就是我們可以分別在父類以及本類中定義各自的context字符串,這樣iOS就能知道移除的是自己的kvo俱诸,而不是父類中的kvo菠劝,避免二次remove造成crash. - 把監(jiān)聽(tīng)到對(duì)象的屬性值改變賦值的時(shí)候,一定要注意監(jiān)聽(tīng)對(duì)象的值的類型.
把監(jiān)聽(tīng)到對(duì)象的屬性值改變賦值的時(shí)候,一定要注意監(jiān)聽(tīng)對(duì)象的值的類型.
把監(jiān)聽(tīng)到對(duì)象的屬性值改變賦值的時(shí)候,一定要注意監(jiān)聽(tīng)對(duì)象的值的類型.
重要的事情說(shuō)三遍 - 如果監(jiān)聽(tīng)一個(gè)對(duì)象的多個(gè)屬性,任何一個(gè)屬性的改變都會(huì)走代理方法,也就是說(shuō)對(duì)屬性的監(jiān)聽(tīng),是分開(kāi)執(zhí)行的.
Demo 下載地址
https://github.com/mancongiOS/KVO.git
說(shuō)明
- KVO注意事項(xiàng)1,2,3條轉(zhuǎn)載于 編程小翁@博客園