iOS Objective-C KVO 常見(jiàn)用法

iOS Objective-C KVO 常見(jiàn)用法

前言

KVOKey-Value Observing是蘋(píng)果提供給開(kāi)發(fā)者的一套鍵值觀察的API怔毛,在我們?nèi)粘i_(kāi)發(fā)中經(jīng)常用到KVO進(jìn)行屬性的觀察,接下來(lái)我們將通過(guò)該篇文章對(duì)KVO的常見(jiàn)用法進(jìn)行總結(jié)。

1. 觀察屬性的變化

1.1 基本的屬性觀察代碼

我們最簡(jiǎn)單的使用KVO就是觀察屬性的變化,在本例中,我們觀察person對(duì)象的name的改變。

添加觀察代碼:

[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];

監(jiān)聽(tīng)觀察代碼:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
  
    NSLog(@"%@ - %@",self, change);
    
}

移除代碼:

- (void)dealloc{
    [self.person removeObserver:self forKeyPath:@"name"];
}

1.2 一些問(wèn)題

Question?

在上面我們是觀察person對(duì)象name屬性的變化,如果我們同樣有一個(gè)新的student對(duì)象也有個(gè)name屬性弃秆,那么我們?cè)诒O(jiān)聽(tīng)代碼處該如何判斷是那個(gè)對(duì)象的name呢?

Answer:

  • 首先我們可以通過(guò)對(duì)object的判斷是否是要觀察的對(duì)象髓帽,但是這樣寫(xiě)代碼就顯得很low菠赚,并且需要觀察的對(duì)象如果太多了,代碼看起來(lái)很復(fù)雜
  • 然后我們還可以用context郑藏,它的定義是(nullable void *)類(lèi)型衡查,平常我們基本都不用這個(gè)屬性,只是傳個(gè)nil必盖,其實(shí)按照官方的說(shuō)法則應(yīng)該傳NULL拌牲,其實(shí)我們可以通過(guò)context字段來(lái)區(qū)分不同的對(duì)象屬性監(jiān)聽(tīng)氯檐。例如:

static void *PersonNameContext  = &PersonNameContext;
static void *StudentNameContext = &StudentNameContext;


[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:PersonNameContext];
[self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:StudentNameContext];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
  
    if (context == PersonNameContext) {
        NSLog(@"person name change %@ - %@",self, change);
    } else if (context == StudentNameContext) {
        NSLog(@"student name change %@ - %@",self, change);
    }
}

2. 嵌套觀察

其實(shí)在我們的開(kāi)發(fā)中經(jīng)常會(huì)有顯示下載進(jìn)度的需求宿饱,下載進(jìn)度由 totalCountcompletedCount 兩個(gè)值組成,如果我們觀察這兩個(gè)值就需要在兩個(gè)地方處理進(jìn)度的改變芯急,這時(shí)候我們希望通過(guò)觀察一個(gè)downloadProgress就能實(shí)現(xiàn)獲取下載進(jìn)度的需求失驶。

2.1 實(shí)現(xiàn)代碼

相關(guān)屬性:

// 相關(guān)屬性
@property (nonatomic, copy) NSString *downloadProgress;
@property (nonatomic, assign) double completedCount;
@property (nonatomic, assign) double totalCount;

downloadProgress Getter:

- (NSString *)downloadProgress{
    if (self.completedCount <= 0) {
        self.completedCount = 0;
    }
    if (self.totalCount <= 0) {
        return @"0";
    }
    return [[NSString alloc] initWithFormat:@"%f",1.0f*self.completedCount/self.totalCount];
}

keyPathsForValuesAffectingValueForKey:

通過(guò)keyPathsForValuesAffectingValueForKey方法實(shí)現(xiàn)多鍵值的觀察土居。

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
    
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"downloadProgress"]) {
        NSArray *affectingKeys = @[@"totalCount", @"completedCount"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}

添加觀察代碼:

[self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL];

監(jiān)聽(tīng)觀察代碼:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
  
    NSLog(@"%@ - %@",self, change);
    
}

當(dāng)totalCountcompletedCount任一值改變后都可以觸發(fā)downloadProgress的改變,在這里最重要的就是通過(guò)+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key方法實(shí)現(xiàn)一組鍵值的觀察嬉探。

3. 觀察可變集合

這里我們?cè)?code>KVC那篇文章中有提到過(guò)擦耀,對(duì)于可變數(shù)組的觀察需要使用mutableArrayValueForKey等相關(guān)方法。其實(shí)主要原因是KVO觀察屬性是基于Setter方法甲馋,但是直接調(diào)用可變數(shù)組的addObject方法是不走Setter的埂奈,所以此時(shí)無(wú)法實(shí)現(xiàn)可變數(shù)組的觀察迄损,其他可變集合同理定躏。

添加觀察代碼:

[self.person addObserver:self forKeyPath:@"dataArray" options:(NSKeyValueObservingOptionNew) context:NULL];

監(jiān)聽(tīng)和移除代碼跟上面的類(lèi)似。這里主要是改變的代碼會(huì)有不同。

改變數(shù)組的代碼:

// 數(shù)組變化(不會(huì)觸發(fā)改變)
[self.person.dataArray addObject:@"1"];
// 數(shù)組變化(會(huì)觸發(fā)改變)
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"2"];

打印結(jié)果:

打印結(jié)果.jpg

通過(guò)上圖結(jié)果我們可以知道痊远,添加@"1"的時(shí)候并不會(huì)觸發(fā)KVO垮抗,在添加@"2"的時(shí)候觸發(fā)了KVO的監(jiān)聽(tīng)改變,所以對(duì)于可變集合的KVO的監(jiān)聽(tīng)需要用對(duì)應(yīng)的mutableXXXValueForKey方法進(jìn)行改變碧聪。

4. 自動(dòng)觀察與手動(dòng)觀察

默認(rèn)情況下冒版,我們只需要按照上面的代碼就可以實(shí)現(xiàn)屬性的觀察,其實(shí)這是由系統(tǒng)完全控制的逞姿,屬于自動(dòng)觀察辞嗡。其實(shí)KVO還給我們提供了手動(dòng)觀察的選項(xiàng)。

如果我們想要開(kāi)啟手動(dòng)觀察就要通過(guò)重寫(xiě)類(lèi)方法+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key滞造,如果返回YES就是自動(dòng)觀察续室,返回NO就是手動(dòng)觀察,根據(jù)方法的我們還可以判斷key值對(duì)不同的key分別實(shí)現(xiàn)自動(dòng)觀察手動(dòng)觀察谒养。

// 自動(dòng)開(kāi)關(guān)
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
    
    if ([key isEqualToString:@"name"]) {
        return NO;
    } else {
        return YES;
    }
}

對(duì)于需要手動(dòng)觀察key在改變前需要調(diào)用willChangeValueForKey方法挺狰,在改變后需要調(diào)用didChangeValueForKey方法,如果不調(diào)用买窟,就不會(huì)觸發(fā)KVO的監(jiān)聽(tīng)丰泊。

示例代碼:

[self.person willChangeValueForKey:@"name"];
self.person.name  = @"null";
[self.person didChangeValueForKey:@"name"];

5. KVO是否需要移除觀察

在實(shí)驗(yàn)中,對(duì)于普通的對(duì)象我們不進(jìn)行移除并沒(méi)有出現(xiàn)問(wèn)題始绍,但是對(duì)于單例對(duì)象就會(huì)有問(wèn)題瞳购。這里就不一一驗(yàn)證了,總是就是一句話

一定要移除觀察亏推!
一定要移除觀察苛败!
一定要移除觀察!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末径簿,一起剝皮案震驚了整個(gè)濱河市罢屈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌篇亭,老刑警劉巖缠捌,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異译蒂,居然都是意外死亡曼月,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)柔昼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)哑芹,“玉大人,你說(shuō)我怎么就攤上這事捕透〈献耍” “怎么了碴萧?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)末购。 經(jīng)常有香客問(wèn)我破喻,道長(zhǎng),這世上最難降的妖魔是什么盟榴? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任曹质,我火速辦了婚禮,結(jié)果婚禮上擎场,老公的妹妹穿的比我還像新娘羽德。我一直安慰自己,他們只是感情好迅办,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布玩般。 她就那樣靜靜地躺著,像睡著了一般礼饱。 火紅的嫁衣襯著肌膚如雪坏为。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,850評(píng)論 1 290
  • 那天镊绪,我揣著相機(jī)與錄音匀伏,去河邊找鬼。 笑死蝴韭,一個(gè)胖子當(dāng)著我的面吹牛够颠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播榄鉴,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼履磨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了庆尘?” 一聲冷哼從身側(cè)響起剃诅,我...
    開(kāi)封第一講書(shū)人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驶忌,沒(méi)想到半個(gè)月后矛辕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡付魔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年聊品,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片几苍。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡翻屈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妻坝,到底是詐尸還是另有隱情伸眶,我是刑警寧澤惊窖,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站赚抡,受9級(jí)特大地震影響爬坑,放射性物質(zhì)發(fā)生泄漏纠屋。R本人自食惡果不足惜涂臣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望售担。 院中可真熱鬧赁遗,春花似錦、人聲如沸族铆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)哥攘。三九已至剖煌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間逝淹,已是汗流浹背耕姊。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留栅葡,地道東北人茉兰。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像欣簇,于是被迫代替她去往敵國(guó)和親规脸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349