iOS鍵值觀察KVO實例詳解

簡介

什么是KVO揖庄?KVO是Key-Value Observing的簡稱,翻譯成中文就是鍵值觀察欠雌。這是iOS支持的一種機制蹄梢,用來做什么呢?我們在開發(fā)應用時經(jīng)常需要進行通信富俄,比如一個model的某個數(shù)據(jù)變化了禁炒,界面上要進行相應的變化,但是如果我們程序并不知道數(shù)據(jù)什么時候會進行變化霍比,總不能一直循環(huán)判斷有沒有變化吧幕袱,那么就需要在數(shù)據(jù)變化時給controlller發(fā)送一個通知,告知我變化了悠瞬,你可以更新顯示內(nèi)容了们豌,通知的方式有很多種涯捻,比如Notification也是其中一種方式,本文要講解的KVO也是其中一種很輕巧的方式望迎。

他的實現(xiàn)機制為障癌,為可能改變的數(shù)據(jù)增加一個觀察者,在上面的說法中這個觀察者就是controller辩尊,它去觀察這個數(shù)據(jù)有沒有發(fā)生變化涛浙,一旦發(fā)生變化,就會得到一個信號摄欲,從而獲取到變化的數(shù)據(jù)轿亮,進行自己要做的操作。

實例效果

image.png

如上圖所示蒿涎,界面上設置兩個label哀托,一個顯示名字,一個顯示分數(shù)劳秋。還有一個按鈕仓手,用來修改分數(shù),現(xiàn)在要做到點擊按鈕分數(shù)變化玻淑。

可能你會覺得很簡單嗽冒,直接在按鈕的響應方法中將分數(shù)的label內(nèi)容修改不就可以了嗎,確實如此补履,但是這里我們不這么做添坊,而是使用KVO來完成。

我們創(chuàng)建一個學生模型箫锤,這個模型有兩個屬性贬蛙,一個為姓名,一個為分數(shù)谚攒。label這是讀取模型的數(shù)據(jù)來進行顯示阳准。

現(xiàn)在我們給這個實例化了的學生模型添加一個觀察者,定義為我要觀察學生模型的分數(shù)變化情況馏臭,這時野蝇,如果這個學生模型的分數(shù)發(fā)生了變化,比如在按鈕響應中只對模型的分數(shù)屬性進行修改括儒,KVO這個機制就會自動給觀察者發(fā)送通知绕沈,說這個屬性變化了,你要做什么操作趕緊做帮寻。

于是我們在觀察者的KVO回調(diào)函數(shù)中進行相應的操作乍狐,如果我們收到了分數(shù)變化的通知,那么就將分數(shù)label的值給修改為當前的分數(shù)规婆。這樣就實現(xiàn)了一套KVO鍵值觀察的流程澜躺,當然最后還缺一步就是移除觀察者蝉稳,不過要在確實需要移除的時候再移除,因為移除后就不再會收到變化的通知了掘鄙。

實現(xiàn)方式

上面例子中進行了一套KVO鍵值觀察的流程耘戚,我們整理一下進行了哪些工作:

  1. 設計界面樣式
  2. 建立學生模型
  3. 對學生的分數(shù)屬性添加觀察
  4. 修改學生的分數(shù)屬性
  5. 在觀察到變化的響應方法中進行界面更新操作
  6. 不再需要觀察的時候移除觀察

現(xiàn)在通過這個例子來一步步講解。

設計樣式

樣式就不說了操漠,兩個label收津,一個按鈕,以及按鈕的響應方法浊伙,都是很常見的撞秋。

建立模型

這個部分,就是新建一個NSObject類嚣鄙,用來作為學生模型吻贿,有兩個屬性:姓名和分數(shù),如下所示:

// StudentModel.h
@interface StudentModel : NSObject

@property (nonatomic, copy) NSString *name;
@property float score;

@end

// ViewController.m
// 在controller中實例化學生模型
self.studentModel = [[StudentModel alloc] init];
[self.studentModel setValue:@"Cloudox" forKey:@"name"];
[self.studentModel setValue:@"89.0" forKey:@"score"];

添加觀察

這一步哑子,才是真正開始使用KVO了舅列。

要使用KVO,至少必須要實現(xiàn)兩個方法:

  • addObserver:forkeyPath:options:context:
  • observeValueForKeyPath:ofObject:change:context:

第二個方法卧蜓,就是用來獲取數(shù)據(jù)變化的通知并進行相應操作的方法帐要,這個我們后面再講,先講第一個方法弥奸,顧名思義榨惠,這就是用來添加觀察者的方法了。

可能你會注意到盛霎,我們上面實例化學生模型的時候赠橙,使用的是 setVlue:forKey: 的形式來設置屬性值的,為什么要這樣設置呢愤炸?聯(lián)想到KVO的名字简烤,鍵值觀察,就能大概明白了摇幻,學生模型的屬性名就相當于key,屬性值就相當于值挥萌。

緊接著就可以對分數(shù)來添加觀察了:

[self.studentModel addObserver:self forKeyPath:@"score" options:NSKeyValueObservingOptionNew             context:nil];

這里使用的就是第一個方法绰姻,有四個參數(shù)。

  • 第一個參數(shù)是觀察者引瀑,這里被觀察者是學生模型狂芋,觀察者是controller,也就是self
  • 第二個參數(shù)是keyPath憨栽,其實也就是要觀察的鍵
  • 第三個是一個options帜矾,這里我們寫的是一個枚舉值翼虫,這個地方可以填幾種值,下面在進行詳細的說明
  • 第四個我們填了nil屡萤,也有其作用珍剑,下面再細說

總之通過這行代碼,我們就對score這個鍵死陆,也即是分數(shù)添加了觀察招拙。

修改數(shù)據(jù)

在按鈕的響應方法中修改學生模型的分數(shù)數(shù)據(jù),同樣使用 setVlue:forKey: 的方式進行設置措译。

接收通知

這里就用到第二個方法:observeValueForKeyPath:ofObject:change:context:

先看看這個例子中的實現(xiàn):

// KVO回調(diào)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"score"]) {
        self.scoreLabel.text = [NSString stringWithFormat:@"Score:%@", [self.studentModel valueForKey:@"score"]];
    }
}

可以看到這個回調(diào)的變化響應方法也有四個參數(shù)别凤,其實就對應上面添加觀察時的四個參數(shù),通過keyPath领虹,我們可以判斷是不是我們想要接收的數(shù)據(jù)變化规哪,判斷它是不是score,其實也就是對不同的被觀察者進行不同的操作塌衰。確實是分數(shù)變化后诉稍,我們就更新界面上的分數(shù)label,用新的分數(shù)來顯示猾蒂。

移除通知

移除通知的方法很簡單均唉,如下:

[self.studentModel removeObserver:self forKeyPath:@"score"];

從觀察者那邊移除對被觀察者特定鍵的觀察。

至此肚菠,一個簡單的KVO流程就走完了舔箭,很簡單對吧。

進階用法

傳遞對象

上面添加觀察者和響應變化的方法中都有一個 context參數(shù)蚊逢,通過這個參數(shù)可以傳遞一些東西层扶,在添加觀察者時設置要傳遞的內(nèi)容,在響應變化時獲得傳遞的內(nèi)容烙荷。

比如我要傳遞一個字符串镜会,在添加觀察者時設置:

[self.studentModel addObserver:self forKeyPath:@"score" options:NSKeyValueObservingOptionNew context:@"heyMe"];// 2.通過context傳遞內(nèi)容給觀察者

這里在context的參數(shù)中就直接設置了要傳遞的對象字符串。然后在變化響應時:

// KVO回調(diào)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"score"]) {
        self.scoreLabel.text = [NSString stringWithFormat:@"Score:%@", [self.studentModel valueForKey:@"score"]];
    }
    NSLog(@"%@", context);// 通過context獲取被觀察者傳遞的內(nèi)容
}

這里就可以輸出context看一下终抽,會得到傳遞過來的字符串內(nèi)容戳表。

options參數(shù)

在添加觀察者時有一個options參數(shù),在回調(diào)獲取變化時有一個change參數(shù)昼伴,這兩個參數(shù)其實是對應的匾旭,都是用來增加傳遞變化的豐富度。

options參數(shù)可以設為:

  • NSKeyValueObservingOptionOld:這表示在回調(diào)獲取變化時可以通過change參數(shù)獲取變化之前的值圃郊;
  • NSKeyValueObservingOptionNew:這表示在回調(diào)獲取變化時可以通過change參數(shù)獲取變化后的值价涝;
  • NSKeyValueObservingOptionInitial:在添加觀察者方法return的時候就發(fā)出一次通知;
  • NSKeyValueObservingOptionPrior:會在觀察的值發(fā)生變化前發(fā)出一次通知持舆,變化后還是會發(fā)出一次通知色瘩,所以變化一次一共會得到兩次通知伪窖。

以上就是options參數(shù),可以看到都是對應change參數(shù)的居兆,用來決定change參數(shù)可以得到什么樣的數(shù)據(jù)覆山,在回調(diào)獲取變化時可以輸出change看一下,就可以知道不同的效果了史辙。

change參數(shù)

在使用change的時候可以通過下面的key來操作:

  • NSKeyValueChangeKindKey:對應NSKeyValueChange的枚舉值
    • NSKeyValueChangeSetting = 1:說明被觀察的數(shù)據(jù)的setter方法被調(diào)用了汹买;
    • NSKeyValueChangeInsertion = 2:當觀察的數(shù)據(jù)是集合時,且對它進行insert操作時會返回該值聊倔;
    • NSKeyValueChangeRemoval = 3:當觀察的數(shù)據(jù)是集合時晦毙,且對它進行remove操作時會返回該值;
    • NSKeyValueChangeReplacement = 4:當觀察的數(shù)據(jù)是集合時耙蔑,且對它進行replace操作時會返回該值见妒。
  • NSKeyValueChangeNewKey:對應options參數(shù)中的NSKeyValueObservingOptionNew,會在其中包含觀察的數(shù)據(jù)變化后的新值
  • NSKeyValueChangeOldKey:對應options參數(shù)中的NSKeyValueObservingOptionOld甸陌,會在其中包含觀察的數(shù)據(jù)變化之前得舊值
  • NSKeyValueChangeIndexesKey:當NSKeyValueChangeKindKey是2须揣、3、4的時候钱豁,也就是說是觀察集合數(shù)據(jù)時耻卡,這個key的值是一個NSIndexSet,包含操作對象的索引集合
  • NSKeyValueChangeNotificationIsPriorKey:包含一個布爾值牲尺,如果options的參數(shù)是NSKeyValueObservingOptionPrior卵酪,也就是會通知兩次,在第一次通知谤碳,也就是改變前的通知時溃卡,會包含這個key

關于這些change的值,都可以輸出到控制臺試一下看看是什么效果蜒简,會有更加直觀的感受瘸羡。

手動通知

之前說的都是自動通知,當添加了觀察者后搓茬,只要發(fā)生改變就會自動通知觀察者犹赖,但有時候我們并不是什么改變都希望得到通知,或者有時候是希望變化到什么情況后再通知卷仑,這就需要改變通知的機制冷尉。默認的實現(xiàn)模式為自動通知的模式,要自定義何時進行通知系枪,就要改成手動通知的模式。

要改成手動通知磕谅,首先要在被觀察者的模型中重寫一個方法 automaticallyNotifiesObserversForKey :

// StudentModel.m
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    BOOL automatic = NO;
    if ([key isEqualToString:@"score"]) {
        automatic = NO;
    } else {
        automatic = [super automaticallyNotifiesObserversForKey:key];
    }
    return automatic;
}

這里我們在學生模型的實現(xiàn)文件中重寫了這個方法私爷,判斷當觀察的key是score分數(shù)時幔荒,就將自動通知關閉带饱,其余的情況還是根據(jù)父類來進行判斷,這樣寫比較保險。

這樣在我們改變學生模型的分數(shù)時骡楼,就不會自動觸發(fā)通知了,要觸發(fā)通知廉白,需要自己進行設置:

// 按鈕響應
- (void)changeScore {
    [self willChangeValueForKey:@"score"];// 改為手動通知
    [self.studentModel setValue:@"99.0" forKey:@"score"];
    [self didChangeValueForKey:@"score"];// 改為手動通知
}

這時就可以觸發(fā)通知了隧饼,如果一個操作會觸發(fā)多個屬性改變,都要發(fā)通知助币,那么需要嵌套通知:

// 按鈕響應
- (void)changeScore {
    [self willChangeValueForKey:@"name"];// 改為手動通知
    [self willChangeValueForKey:@"score"];// 改為手動通知
    [self.studentModel setValue:@"Cloud" forKey:@"name"];
    [self.studentModel setValue:@"99.0" forKey:@"score"];
    [self didChangeValueForKey:@"score"];// 改為手動通知
    [self didChangeValueForKey:@"name"];// 改為手動通知
}

而在一個一對多的關系中浪听,比如集合,你必須注意不僅僅是這個key改變了眉菱,還有它改變的類型以及索引迹栓,也就是我們change中對應的幾種涉及到集合的東西,如下所示:

 - (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes {
        [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@"transactions"];

           // Remove the transaction objects at the specified indexes.

        [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@"transactions"];
}

鍵值依賴

其實關于KVO還有一個重要的點是鍵值依賴俭缓,也就是說一個屬性的值依賴于對象中的其他屬性克伊,當那些屬性變化后,這個屬性的值自動被通知到進行修改华坦,不過這個點沒太弄明白愿吹,蘋果給的例子有點不清不楚的,再研究一下吧惜姐。


示例工程:https://github.com/Cloudox/KVODemo


查看作者首頁

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末犁跪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子载弄,更是在濱河造成了極大的恐慌耘拇,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宇攻,死亡現(xiàn)場離奇詭異惫叛,居然都是意外死亡,警方通過查閱死者的電腦和手機逞刷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門嘉涌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人夸浅,你說我怎么就攤上這事仑最。” “怎么了帆喇?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵警医,是天一觀的道長。 經(jīng)常有香客問我,道長预皇,這世上最難降的妖魔是什么侈玄? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮吟温,結(jié)果婚禮上序仙,老公的妹妹穿的比我還像新娘。我一直安慰自己鲁豪,他們只是感情好潘悼,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著爬橡,像睡著了一般治唤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上堤尾,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天肝劲,我揣著相機與錄音,去河邊找鬼郭宝。 笑死辞槐,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的粘室。 我是一名探鬼主播榄檬,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼衔统!你這毒婦竟也來了鹿榜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤锦爵,失蹤者是張志新(化名)和其女友劉穎舱殿,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體险掀,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡沪袭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了樟氢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冈绊。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖埠啃,靈堂內(nèi)的尸體忽然破棺而出死宣,到底是詐尸還是另有隱情,我是刑警寧澤碴开,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布毅该,位于F島的核電站博秫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鹃骂。R本人自食惡果不足惜台盯,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望畏线。 院中可真熱鬧,春花似錦良价、人聲如沸寝殴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚣常。三九已至,卻和暖如春痊银,著一層夾襖步出監(jiān)牢的瞬間抵蚊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工溯革, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贞绳,地道東北人。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓致稀,卻偏偏與公主長得像冈闭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抖单,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

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