KVO解析(二) —— 一個簡單的KVO實現(xiàn)

版本記錄

版本號 時間
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í)行如下步驟:

還要注意:并非所有類都符合所有屬性的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)更有效的通知解析犁柜。 下面顯示了以這種方式選擇的balanceinterestRate屬性的示例上下文洲鸠。

static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;

下面的例子,展示的是一個Person對象是如何利用給定的上下文指針注冊自己作為Account對象balanceinterestRate屬性的觀察者的馋缅。

- (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ù)注冊觀察者時指定的選項,更改字典中的NSKeyValueChangeOldKeyNSKeyValueChangeNewKey條目包含更改前后的屬性值数初。 如果屬性是對象找爱,則直接提供該值。 如果屬性是標(biāo)量或C結(jié)構(gòu)體泡孩,則該值將包裝在NSValue對象中(與鍵值編碼KVC一樣)车摄。

如果觀察到的屬性是一對多關(guān)系,NSKeyValueChangeKindKey條目還指示關(guān)系中的對象是否分別插入仑鸥,刪除或替換吮播,分別返回NSKeyValueChangeInsertionNSKeyValueChangeRemovalNSKeyValueChangeReplacement眼俊。

下面展示的是Person觀察者實現(xiàn)的方法observeValueForKeyPath:ofObject:change:context:意狠,并輸出balanceinterestRate屬性代表的新舊值。

- (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對象移除自己作為balanceinterestRate的觀察者。

- (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)的錯誤。 典型的模式是在觀察者初始化期間(例如在initviewDidLoad中)注冊為觀察者馏谨,并在釋放(通常在dealloc中)時注銷别渔,確保正確配對和有序添加和刪除消息,并且觀察者要在自己在內(nèi)存中釋放之前移除自己作為其他屬性或?qū)ο蟮挠^察者惧互。

后記

未完哎媚,待續(xù)~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市喊儡,隨后出現(xiàn)的幾起案子拨与,更是在濱河造成了極大的恐慌,老刑警劉巖艾猜,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件买喧,死亡現(xiàn)場離奇詭異,居然都是意外死亡匆赃,警方通過查閱死者的電腦和手機(jī)淤毛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來算柳,“玉大人低淡,你說我怎么就攤上這事〔壕樱” “怎么了查牌?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長滥壕。 經(jīng)常有香客問我纸颜,道長,這世上最難降的妖魔是什么绎橘? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任胁孙,我火速辦了婚禮,結(jié)果婚禮上称鳞,老公的妹妹穿的比我還像新娘涮较。我一直安慰自己,他們只是感情好冈止,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布狂票。 她就那樣靜靜地躺著,像睡著了一般熙暴。 火紅的嫁衣襯著肌膚如雪闺属。 梳的紋絲不亂的頭發(fā)上慌盯,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音掂器,去河邊找鬼亚皂。 笑死,一個胖子當(dāng)著我的面吹牛国瓮,可吹牛的內(nèi)容都是我干的灭必。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼乃摹,長吁一口氣:“原來是場噩夢啊……” “哼禁漓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起峡懈,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤璃饱,失蹤者是張志新(化名)和其女友劉穎与斤,沒想到半個月后肪康,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡撩穿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年磷支,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片食寡。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡雾狈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抵皱,到底是詐尸還是另有隱情善榛,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布呻畸,位于F島的核電站移盆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏伤为。R本人自食惡果不足惜咒循,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绞愚。 院中可真熱鬧叙甸,春花似錦、人聲如沸位衩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽糖驴。三九已至僚祷,卻和暖如春哪痰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背久妆。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工晌杰, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人筷弦。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓肋演,卻偏偏與公主長得像,于是被迫代替她去往敵國和親烂琴。 傳聞我的和親對象是個殘疾皇子爹殊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容