版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2017.09.14 |
前言
KVO
具有更強大的功能严衬,是蘋果給我們的一個回調(diào)機(jī)制,在某個對象注冊監(jiān)聽者后抚笔,在被監(jiān)聽的對象發(fā)生改變時扶认,對象會發(fā)送一個通知給監(jiān)聽者,以便監(jiān)聽者執(zhí)行回調(diào)操作塔沃。接下來幾篇就詳細(xì)的解析一下KVO蝠引。感興趣的可以看上面幾篇。
1. KVO解析(一) —— 基本了解
鍵值觀察步驟
為確保對象可以接受鍵值觀察的通知蛀柴,必須執(zhí)行如下步驟:
為被觀察的對象注冊觀察者螃概,使用方法addObserver:forKeyPath:options:context:
實現(xiàn)方法observeValueForKeyPath:ofObject:change:context:,在觀察者的內(nèi)部接收改變的通知消息鸽疾。
當(dāng)不再需要接收消息的時候吊洼,使用方法removeObserver:forKeyPath:移除通知,還有一點制肮,就是要在觀察者從內(nèi)存釋放之前調(diào)用此方法冒窍。
還要注意:并非所有類都符合所有屬性的KVO。 您可以按照KVO合規(guī)性中描述的步驟確保您自己的類符合KVO豺鼻。 通常综液,如果Apple提供的框架中的屬性通常被記錄,那么它們符合KVO的合規(guī)性儒飒。
注冊觀察者
觀察對象首先通過發(fā)送一個addObserver:forKeyPath:options:context:
消息來注冊觀察對象谬莹,將自身傳遞給觀察者和要觀察的屬性的關(guān)鍵路徑。 觀察者另外指定一個選項參數(shù)option
和一個上下文指針context
來管理通知的各個方面桩了。
1. Options
指定為選項常量的按位OR的options參數(shù)影響通知中提供的change
字典的內(nèi)容以及生成通知的方式附帽。
您選擇通過指定選項NSKeyValueObservingOptionOld
從更改之前接收觀察到的屬性的值。 您使用選項NSKeyValueObservingOptionNew
請求屬性的新值井誉。 您可以使用這些選項的按位OR來收到舊值和新值蕉扮。
您指示被觀察的對象發(fā)送立即更改通知(在addObserver:forKeyPath:options:context:returns
之前),其選項為NSKeyValueObservingOptionInitial
颗圣。 您可以使用此額外的一次性通知來建立觀察者中的屬性的初始值喳钟。
您可以通過包含NSKeyValueObservingOptionPrior
選項來指示觀察對象在屬性更改之前發(fā)送通知(除更改之后的通常通知之外)。 change
更改字典表示通過將NSKeyValueChangeNotificationIsPriorKey
的關(guān)鍵字與NSNumber
包裝YES的值相關(guān)聯(lián)的代替通知在岂。 那個key是不存在的荚藻。 當(dāng)觀察者自己的KVO合規(guī)性要求它調(diào)用一個取決于所觀察屬性的其中一個屬性的-willChange ...
方法時,可以使用預(yù)置通知洁段。 通常的修改后通知太遲了应狱,無法及時調(diào)用willChange ...
。
2. Context
addObserver:forKeyPath:options:context:
方法中的上下文指針包含將在相應(yīng)的更改通知中傳回給觀察者的任意數(shù)據(jù)祠丝。 您可以指定NULL
并完全依賴于鍵路徑字符串來確定更改通知的來源疾呻,但是由于不同的原因,此方法可能會導(dǎo)致超類也遵循相同鍵路徑的對象的問題写半。
更安全和更可擴(kuò)展的方法是使用上下文來確保您收到的通知注定給您的觀察者岸蜗,而不是超類。
你的類中唯一命名的靜態(tài)變量的地址是一個很好的上下文叠蝇。 在超類或子類中以類似方式選擇的上下文將不太可能重疊璃岳。 您可以為整個類選擇單個上下文,并依靠通知消息中的鍵路徑字符串來確定更改的內(nèi)容。 或者铃慷,您可以為每個觀察到的鍵路徑創(chuàng)建一個獨特的上下文单芜,這樣可以避免完全需要字符串比較,從而實現(xiàn)更有效的通知解析犁柜。 下面顯示了以這種方式選擇的balance
和interestRate
屬性的示例上下文洲鸠。
static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
下面的例子,展示的是一個Person對象是如何利用給定的上下文指針注冊自己作為Account
對象balance
和interestRate
屬性的觀察者的馋缅。
- (void)registerAsObserverForAccount:(Account*)account
{
[account addObserver:self
forKeyPath:@"balance"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:PersonAccountBalanceContext];
[account addObserver:self
forKeyPath:@"interestRate"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:PersonAccountInterestRateContext];
}
這里還要注意:鍵值觀察addObserver:forKeyPath:options:context:
方法不保持對觀察對象扒腕,被觀察對象或上下文的強引用。如果需要的話萤悴, 您應(yīng)該確保在必要時保持對觀察瘾腰,被觀察,對象和上下文的強烈引用覆履。
接收Change的通知
當(dāng)被觀察對象屬性的值發(fā)生變化時居灯,觀察者會接收到observeValueForKeyPath:ofObject:change:context:
消息。 所有觀察者必須實現(xiàn)這種方法内狗。
被觀察對象提供了鍵值路徑并觸發(fā)通知怪嫌,并把自己作為關(guān)聯(lián)對象,還有關(guān)于改變的詳細(xì)內(nèi)容的字典柳沙、當(dāng)觀察者注冊此鍵值路徑時提供的上下文指針岩灭。
更改字典條目NSKeyValueChangeKindKey
提供有關(guān)發(fā)生的更改類型的信息。 如果觀察到的對象的值已更改赂鲤,NSKeyValueChangeKindKey
條目將返回NSKeyValueChangeSetting
噪径。 根據(jù)注冊觀察者時指定的選項,更改字典中的NSKeyValueChangeOldKey
和NSKeyValueChangeNewKey
條目包含更改前后的屬性值数初。 如果屬性是對象找爱,則直接提供該值。 如果屬性是標(biāo)量或C結(jié)構(gòu)體泡孩,則該值將包裝在NSValue
對象中(與鍵值編碼KVC一樣)车摄。
如果觀察到的屬性是一對多關(guān)系,NSKeyValueChangeKindKey
條目還指示關(guān)系中的對象是否分別插入仑鸥,刪除或替換吮播,分別返回NSKeyValueChangeInsertion
,NSKeyValueChangeRemoval
或NSKeyValueChangeReplacement
眼俊。
下面展示的是Person觀察者實現(xiàn)的方法observeValueForKeyPath:ofObject:change:context:
意狠,并輸出balance
和interestRate
屬性代表的新舊值。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == PersonAccountBalanceContext) {
// Do something with the balance…
}
else if (context == PersonAccountInterestRateContext) {
// Do something with the interest rate…
}
else {
// Any unrecognized context must belong to super
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
當(dāng)你注冊觀察者時指定的上下文是NULL
時疮胖,您將通知的鍵路徑與您觀察到的鍵路徑進(jìn)行比較环戈,以確定發(fā)生了什么變化闷板。 如果您對所有觀察到的鍵路徑使用單個上下文,則首先根據(jù)通知的上下文測試院塞,并找到匹配項遮晚,使用鍵路徑字符串比較來確定特定的更改。 如果您為每個鍵路徑提供了唯一的上下文迫悠,如下所示鹏漆,一系列簡單的指針比較可以同時告知您該通知是否適用于此觀察者巩梢,如果是创泄,則鍵路徑已更改。
在任何情況下括蝠,觀察者應(yīng)該總是調(diào)用超類的實現(xiàn)observeValueForKeyPath:ofObject:change:context:
當(dāng)它不識別上下文(或在簡單的情況下鞠抑,任何鍵路徑)時,因為這意味著超類也已經(jīng)注冊通知忌警。
注意:如果通知傳播到類層次結(jié)構(gòu)的頂部搁拙,NSObject
將拋出一個NSInternalInconsistencyException
,因為這是一個編程錯誤:一個子類不能使用它注冊的通知法绵。
觀察者的移除
通過向被觀察對象發(fā)送消息removeObserver:forKeyPath:context:
箕速,指定觀察對象、鍵路徑和上下文朋譬,從而移除鍵值觀察對象盐茎,下面列出的是Person對象移除自己作為balance
和interestRate
的觀察者。
- (void)unregisterAsObserverForAccount:(Account*)account
{
[account removeObserver:self
forKeyPath:@"balance"
context:PersonAccountBalanceContext];
[account removeObserver:self
forKeyPath:@"interestRate"
context:PersonAccountInterestRateContext];
}
接收到消息removeObserver:forKeyPath:context:
之后徙赢,觀察者不再收到任何指定的鍵和對象的observeValueForKeyPath:ofObject:change:context:
消息字柠。
當(dāng)移除觀察者時,還需要謹(jǐn)記幾個問題:
如果你在移除觀察者時狡赐,這個觀察者尚未注冊窑业,就會導(dǎo)致
NSRangeException
錯誤,相對于方法addObserver:forKeyPath:options:context:
枕屉,你可以調(diào)用方法removeObserver:forKeyPath:context:
一次常柄,或者如果這個在你的app里面是不可行的,那么就在try/catch block
里面處理這個潛在的錯誤和例外搀擂。當(dāng)
dellocated
自己時拐纱,觀察者不會自動移除自己。 觀察到的對象繼續(xù)發(fā)送通知哥倔,忽視觀察者的狀態(tài)秸架。 但是,像發(fā)送給已釋放對象的任何其他消息一樣咆蒿,更改通知會觸發(fā)內(nèi)存訪問異常东抹。 因此蚂子,您可以確保觀察者在消失之前消除自己。如果該對象是觀察者或觀察者缭黔,則該協(xié)議無法詢問對象食茎。 構(gòu)建您的代碼以避免出現(xiàn)相關(guān)的錯誤。 典型的模式是在觀察者初始化期間(例如在
init
或viewDidLoad
中)注冊為觀察者馏谨,并在釋放(通常在dealloc
中)時注銷别渔,確保正確配對和有序添加和刪除消息,并且觀察者要在自己在內(nèi)存中釋放之前移除自己作為其他屬性或?qū)ο蟮挠^察者惧互。
后記
未完哎媚,待續(xù)~~~