iOS 關(guān)于KVO的一些總結(jié)

本文參考鏈接:

iOS KVO詳解

Foundation: NSKeyValueObserving(KVO)

KVO原理分析及使用進(jìn)階


概述

KVO是基于觀察者模式來(lái)實(shí)現(xiàn)的

觀察者模式:一個(gè)目標(biāo)對(duì)象管理所有依賴于它的觀察者對(duì)象黔牵,并在它自身的狀態(tài)改變時(shí)主動(dòng)通知觀察者對(duì)象晓勇。這個(gè)主動(dòng)通知通常是通過(guò)調(diào)用各個(gè)觀察者對(duì)象所提供的接口方法來(lái)實(shí)現(xiàn)的堰酿。觀察者模式較完美地將目標(biāo)對(duì)象與觀察者對(duì)象解耦****窥浪。

KVO全稱為Key-Value Observing镊逝,是Foundation框架提供的一種機(jī)制,使用KVO旺拉,可以方便地對(duì)指定對(duì)象的某個(gè)屬性進(jìn)行觀察产上,當(dāng)屬性發(fā)生變化時(shí),進(jìn)行通知****蛾狗。

使用KVO只需要兩個(gè)步驟

  1. 注冊(cè)O(shè)bserver晋涣;

  2. 接收通知。

1沉桌、注冊(cè)O(shè)bserver

使用下面方法注冊(cè)O(shè)bserver

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

即:

addObserver:forKeyPath:options:context:

參數(shù)含義:

  • observer:觀察者谢鹊,需要響應(yīng)屬性變化的對(duì)象算吩。該對(duì)象必須實(shí)現(xiàn) observeValueForKeyPath:ofObject:change:context: 方法。
  • keyPath:要觀察的屬性名稱佃扼。要和屬性聲明的名稱一致偎巢。
  • options:對(duì)KVO機(jī)制進(jìn)行配置,修改KVO通知的時(shí)機(jī)以及通知的內(nèi)容兼耀。
  • context:context是一個(gè)c指針压昼,可以傳入任意類型的對(duì)象,在觀察者接收通知回調(diào)的方法 observeValueForKeyPath:ofObject:change:context: 中可以接收到這個(gè)對(duì)象瘤运,是KVO中的一種傳值方式窍霞。這個(gè)參數(shù)可以用來(lái)區(qū)分同一對(duì)象對(duì)同一個(gè)屬性的多個(gè)不同的監(jiān)聽(tīng)
  1. 注意:分清觀察者對(duì)象和目標(biāo)對(duì)象拯坟,調(diào)用 addObserver:forKeyPath:options:context: 方法的對(duì)象是目標(biāo)對(duì)象但金,observer是觀察者對(duì)象,keyPath是目標(biāo)對(duì)象的屬性郁季。

  2. 注意冷溃,在注冊(cè)了Observer后,一定要在合適時(shí)機(jī)移除注冊(cè)巩踏,否則會(huì)crash秃诵。移除注冊(cè)的兩種方法:

- (void)removeObserver:(NSObject *)anObserver
            forKeyPath:(NSString *)keyPath

- (void)removeObserver:(NSObject *)observer
            forKeyPath:(NSString *)keyPath
               context:(void *)context

蘋(píng)果官方推薦的方式是续搀,在init的時(shí)候進(jìn)行addObserver塞琼,在dealloc時(shí)removeObserver,這樣可以保證add和remove是成對(duì)出現(xiàn)的禁舷,是一種比較理想的使用方式彪杉。

第二種方法帶有context屬性,主要是用來(lái)區(qū)分不同的觀察者Observer的牵咙。

如果observer沒(méi)有監(jiān)聽(tīng)keyPath屬性派近,則調(diào)用這兩個(gè)方法會(huì)拋出異常并崩潰。所以洁桌,必須確保先注冊(cè)了觀察者渴丸,才能調(diào)用移除方法。另凌。實(shí)際上谱轨,在添加觀察者的時(shí)候,觀察者對(duì)象與被觀察屬性所屬的對(duì)象都不會(huì)被retain吠谢,然而在這些對(duì)象被釋放后土童,相關(guān)的監(jiān)聽(tīng)信息卻還存在,KVO做的處理是直接讓程序崩潰工坊。

  1. options參數(shù)是一個(gè)枚舉類型献汗,共有四種取值方式:
enum {
NSKeyValueObservingOptionNew = 0x01, //新值
NSKeyValueObservingOptionOld = 0x02, //舊值
NSKeyValueObservingOptionInitial = 0x04, //
NSKeyValueObservingOptionPrior = 0x08
};
  • NSKeyValueObservingOptionNew:接收方法中使用change參數(shù)傳入變化后的新值敢订,鍵為:NSKeyValueChangeNewKey;

  • NSKeyValueObservingOptionOld:接收方法中使用change參數(shù)傳入變化前的舊值罢吃,鍵為:NSKeyValueChangeOldKey楚午;

  • NSKeyValueObservingOptionInitial:注冊(cè)之后立即調(diào)用一次接收方法。如果還如果配置了NSKeyValueObservingOptionNew刃麸,change參數(shù)內(nèi)容會(huì)包含新值醒叁,鍵為:NSKeyValueChangeNewKey。

  • NSKeyValueObservingOptionPrior:如果加入這個(gè)參數(shù)泊业,接收方法會(huì)在變化前后分別調(diào)用一次把沼,共兩次,變化前的通知change參數(shù)包含notificationIsPrior = 1吁伺。其他內(nèi)容根據(jù)NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld的配置確定饮睬。

  1. 注意:options參數(shù)可以配置多個(gè),如:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld篮奄,使用 | 或運(yùn)算符連接捆愁。

  2. 調(diào)用addObserver:forKeyPath:options:context:方法時(shí),觀察者對(duì)象與被觀察屬性所屬的對(duì)象都不會(huì)被retain窟却,也就是說(shuō)昼丑,引用計(jì)數(shù)不會(huì)加1

  3. 可以重復(fù)添加監(jiān)聽(tīng):可以多次調(diào)用addObserver:..方法夸赫,將同一對(duì)象注冊(cè)為同一屬性的的觀察者(參數(shù)可以完全相同菩帝,可以使用context參數(shù)進(jìn)行區(qū)分)。這些觀察者會(huì)并存茬腿。

2呼奢、接收通知

當(dāng)被監(jiān)聽(tīng)的屬性的值發(fā)生變化時(shí),KVO會(huì)自動(dòng)通知注冊(cè)了的觀察者切平。

上文提到握础,觀察者必須實(shí)現(xiàn)以下方法,這個(gè)方法就是觀察者接收通知的方法

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

參數(shù):

  • object:目標(biāo)對(duì)象悴品,即所監(jiān)聽(tīng)的對(duì)象禀综,也就是所監(jiān)聽(tīng)的屬性所屬的對(duì)象

  • change:是傳入的變化量苔严,通過(guò)在注冊(cè)時(shí)用options參數(shù)進(jìn)行的配置定枷,會(huì)包含不同的內(nèi)容。

  1. change參數(shù)

除了根據(jù)options參數(shù)控制的change參數(shù)內(nèi)容邦蜜,默認(rèn)change參數(shù)會(huì)包含一個(gè)NSKeyValueChangeKindKey鍵值對(duì)依鸥,傳遞被監(jiān)聽(tīng)屬性的變化類型

typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4,
};
  • NSKeyValueChangeSetting:屬性的值被重新設(shè)置

  • NSKeyValueChangeInsertion悼沈、NSKeyValueChangeRemoval贱迟、NSKeyValueChangeReplacement:表示更改的是集合屬性姐扮,分別代表插入、刪除衣吠、替換操作茶敏。

  • 如果NSKeyValueChangeKindKey參數(shù)是針對(duì)集合屬性的三個(gè)之一,change參數(shù)還會(huì)包含一個(gè)NSKeyValueChangeIndexesKey鍵值對(duì)缚俏,表示變化的index惊搏。

  1. chang字典里,新值的key為“new”忧换,舊值的key為“old”恬惯,變化類型的key為“kind”
3亚茬、示例

注意酪耳,KVO的運(yùn)行是通過(guò)重寫(xiě)setter方法來(lái)觸發(fā)通知機(jī)制的,也就是說(shuō)刹缝,如果你直接賦值給實(shí)例變量而不是使用屬性賦值的話碗暗,是不會(huì)觸發(fā)KVO的。也就是說(shuō)梢夯,下面的self.str如果換成了_str是無(wú)效的言疗,因?yàn)閟elf.str賦值時(shí)調(diào)用了setter方法。但是使用KVC來(lái)給實(shí)例變量賦值颂砸,會(huì)觸發(fā)KVO噪奄。這點(diǎn)下面會(huì)詳細(xì)說(shuō)明。

#import "ViewController.h"

@interface ViewController ()

@property (copy, nonatomic) NSString *str;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.str = @"1111111";
    
    [self addObserver:self forKeyPath:@"str" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:nil];
    
    self.str = @"2222222";
    self.str = @"3333333";

}

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

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

@end

4沾凄、自動(dòng)通知和手動(dòng)通知

上面提到梗醇,KVO默認(rèn)會(huì)自動(dòng)通知觀察者知允。取消自動(dòng)通知的方法是實(shí)現(xiàn)下面的類方法,通過(guò)返回NO來(lái)取消自動(dòng)通知温鸽。

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key

系統(tǒng)也會(huì)單獨(dú)針對(duì)這個(gè)屬性自動(dòng)生成相關(guān)的類方法保屯,是否自動(dòng)通知這個(gè)屬性被改變,也可以單獨(dú)重寫(xiě)這個(gè)類方法:

//假如有一個(gè)屬性
@property (copy, nonatomic) NSString *str;

//則系統(tǒng)會(huì)自動(dòng)生成關(guān)于這個(gè)屬性的類方法,是否自動(dòng)通知
+ (BOOL)automaticallyNotifiesObserversOfTest;

針對(duì)非自動(dòng)通知的屬性涤垫,可以分別在變化之前和之后手動(dòng)調(diào)用如下方法(will在前姑尺,did在后)來(lái)手動(dòng)通知觀察者:

- (will/did)ChangeValueForKey:
- (will/did)ChangeValueForKey:withSetMutation:usingObjects:
- (will/did)Change:valuesAtIndexes:forKey:

手動(dòng)通知的好處就是,可以靈活加上自己想要的判斷條件蝠猬,事實(shí)上自動(dòng)通知也是框架通過(guò)調(diào)用這些方法實(shí)現(xiàn)的切蟋。

需要注意的是,對(duì)于對(duì)象中其它沒(méi)有處理的屬性榆芦,我們需要調(diào)用[super automaticallyNotifiesObserversForKey:key]柄粹,以避免無(wú)意中修改了父類的屬性的處理方式喘鸟。

下面的代碼是在setter方法中使用 -(will/did)ChangeValueForKey: 方法加上了KVO的通知,如果是在 setter 方法之外改變了實(shí)例變量驻右,且希望這種修改被觀察者監(jiān)聽(tīng)到什黑,則需要像在setter方法里面做一樣的處理

@interface ViewController ()
@property (copy, nonatomic) NSString *str;
@end

@implementation ViewController

- (void)setStr:(NSString *)str{
    
    [self willChangeValueForKey:@"str"];
    
    _str = [str copy];
    
    [self didChangeValueForKey:@"str"];
    
}

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
    
    if ([key isEqualToString:@"str"]) {
        return NO;
    }
    return [super automaticallyNotifiesObserversForKey:key];
}

//也可以是以下方法:
//+ (BOOL)automaticallyNotifiesObserversOfStr{
//
//    return NO;
//
//}

- (void)viewDidLoad {
    [super viewDidLoad];

    self.str = @"1111111";
    
    [self addObserver:self forKeyPath:@"str" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:nil];
    
    self.str = @"2222222";
    self.str = @"3333333";

}

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

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

@end

5堪夭、KVO實(shí)現(xiàn)原理

KVO的實(shí)現(xiàn)是基于runtime運(yùn)行時(shí)機(jī)制的愕把,下面就來(lái)詳細(xì)介紹一下原理,如下圖:

KVO實(shí)現(xiàn)原理
  1. 當(dāng)某個(gè)類的對(duì)象第一次被觀察時(shí)森爽,系統(tǒng)就會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類恨豁,在這個(gè)派生類中重寫(xiě)****基類中任何**被觀察屬性的 **setter 方法****。
  2. 派生類在被重寫(xiě)的 setter 方法中實(shí)現(xiàn)真正的通知機(jī)制爬迟,就如前面手動(dòng)實(shí)現(xiàn)鍵值觀察那樣圣絮。這么做是基于設(shè)置屬性會(huì)調(diào)用 setter 方法,而通過(guò)重寫(xiě)就獲得了 KVO 需要的通知機(jī)制雕旨。當(dāng)然前提是要通過(guò)遵循 KVO 的屬性設(shè)置方式來(lái)變更屬性值扮匠,如果僅是直接修改屬性對(duì)應(yīng)的成員變量,是無(wú)法實(shí)現(xiàn) KVO 的凡涩。
  3. 同時(shí)派生類還重寫(xiě)了 class 方法以“欺騙”外部調(diào)用者它就是起初的那個(gè)類棒搜。然后系統(tǒng)將這個(gè)對(duì)象的 isa 指針指向這個(gè)新誕生的派生類因此這個(gè)對(duì)象就成為該派生類的對(duì)象了活箕,因而在該對(duì)象上對(duì) setter 的調(diào)用就會(huì)調(diào)用重寫(xiě)的 setter力麸,從而激活鍵值通知機(jī)制。此外育韩,派生類還重寫(xiě)了 dealloc 方法來(lái)釋放資源克蚂。

概括總結(jié)一下:KVO的實(shí)現(xiàn)原理,KVO是基于Runtime機(jī)制的筋讨,當(dāng)某個(gè)類的對(duì)象第一次被觀察時(shí)埃叭,在運(yùn)行時(shí)會(huì)動(dòng)態(tài)地生成一個(gè)派生類,派生類會(huì)重寫(xiě)setter方法實(shí)現(xiàn)通知機(jī)制悉罕,并重寫(xiě)class方法赤屋,使對(duì)象的isa指針指向該派生類,以及重寫(xiě)delloc方法來(lái)釋放資源壁袄。

6. KVO 和線程

一個(gè)需要注意的地方是类早,KVO行為是同步的,并且是在與觀察的屬性發(fā)生變化同樣的線程上嗜逻。沒(méi)有隊(duì)列或者 Run-loop 的處理涩僻。手動(dòng)或者自動(dòng)調(diào)用 -didChange... 會(huì)觸發(fā) KVO 通知。

所以,當(dāng)我們?cè)噲D從其他線程改變屬性值的時(shí)候我們應(yīng)當(dāng)十分小心逆日,除非能確定所有的觀察者都用線程安全的方法處理 KVO 通知恼琼。通常來(lái)說(shuō),我們不推薦把 KVO 和多線程混起來(lái)屏富。如果我們要用多個(gè)隊(duì)列和線程晴竞,我們不應(yīng)該在它們互相之間用 KVO。

KVO 是同步運(yùn)行的這個(gè)特性非常強(qiáng)大狠半,只要我們?cè)趩我痪€程上面運(yùn)行(比如主隊(duì)列 main queue)噩死,KVO 會(huì)保證下列兩種情況的發(fā)生:

首先,如果我們調(diào)用一個(gè)支持 KVO 的 setter 方法神年,如下所示:

self.exchangeRate = 2.345;

KVO 能保證所有 exchangeRate 的觀察者在 setter 方法返回前被通知到已维。

其次,如果某個(gè)鍵被觀察的時(shí)候附上了 NSKeyValueObservingOptionPrior (改變前后各調(diào)用接受方法一次) 選項(xiàng)已日,直到 -observeValueForKeyPath... 被調(diào)用之前垛耳, exchangeRate 的 存取方法都會(huì)返回同樣的值。

7飘千、重要注意點(diǎn)
  1. 可以通過(guò) KVO 在 Model 和 Controller 之間進(jìn)行通信堂鲜。

  2. 在類的內(nèi)部,要區(qū)分 self.str 和 _str护奈,賦值給成員變量是不會(huì)觸發(fā) KVO 回調(diào)的缔莲,因?yàn)橘x值給成員變量是不會(huì)調(diào)用 setter 方法的。KVO 是通過(guò) 重寫(xiě)的setter方法來(lái)觸發(fā)的霉旗。點(diǎn)語(yǔ)法的調(diào)用是通過(guò)存取方法來(lái)訪問(wèn)的痴奏,例如:student.name = @"xds";

  3. 但是可以通過(guò) KVC 來(lái)觸發(fā) KVO 的回調(diào)函數(shù),也就是說(shuō)對(duì)成員變量可以使用 KVC 來(lái)觸發(fā) KVO厌秒。對(duì)一個(gè)實(shí)例變量調(diào)用KVC,KVC內(nèi)部主動(dòng)調(diào)用了對(duì)象的willChangeValueForKey:和didChangeValueForKey: 這兩個(gè)方法,所以會(huì)觸發(fā)KVO操作读拆。

  4. 調(diào)用KVO時(shí)需要傳入一個(gè)keyPath,由于keyPath是字符串的形式鸵闪,所以其對(duì)應(yīng)的屬性發(fā)生改變后檐晕,字符串沒(méi)有改變?nèi)菀讓?dǎo)致Crash。我們可以利用系統(tǒng)的反射機(jī)制將keyPath反射出來(lái)岛马,這樣編譯器可以在@selector()中進(jìn)行合法性檢查棉姐。

NSStringFromSelector(@selector(isFinished))
  1. 觸發(fā) KVO 的三種方式
    1. 如果使用了 KVC 訪問(wèn)屬性或成員變量屠列,如果有訪問(wèn)器方法啦逆,則運(yùn)行時(shí)會(huì)在訪問(wèn)器方法中調(diào)用 will/didChangeValueForKey: 方法;沒(méi)有訪問(wèn)器方法笛洛,運(yùn)行時(shí) 會(huì)在 setValue:forKey: 方法中調(diào)用 will/didChangeValueForKey: 方法夏志。
    2. 直接使用了訪問(wèn)器方法(點(diǎn)語(yǔ)法),會(huì)在運(yùn)行時(shí)重寫(xiě) setter 方法,調(diào)用 will/didChangeValueForKey: 方法沟蔑;
    3. 顯示調(diào)用 will/didChangeValueForKey: 方法湿诊。
8、計(jì)算屬性(注冊(cè)依賴鍵)

有時(shí)候瘦材,我們監(jiān)聽(tīng)的某個(gè)屬性可能會(huì)依賴于其它多個(gè)屬性的變化(類似于swift厅须,可以稱之為計(jì)算屬性),不管依賴的哪個(gè)屬性發(fā)生了變化食棕,都會(huì)導(dǎo)致計(jì)算屬性的變化朗和。

我們首先要確定計(jì)算屬性與所依賴屬性的關(guān)系。

假如有一個(gè)Student類簿晓,其resume屬性依賴于name眶拉、age屬性。

//Student.h

@interface Student : NSObject

@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;

@property (copy, nonatomic) NSString *resume;//簡(jiǎn)歷信息
@end


//Student.m

@implementation Student

- (NSString *)resume{
    
    return [NSString stringWithFormat:@"name = %@,age = %ld",self.name,self.age];
}

@end

定義了這種依賴關(guān)系后憔儿,我們就需要以某種方式告訴KVO忆植,當(dāng)我們的被依賴屬性name和age修改時(shí),要發(fā)送resume屬性被修改的通知谒臼。此時(shí)朝刊,我們需要重寫(xiě)NSKeyValueObserving協(xié)議的方法

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key

這個(gè)方法返回的是一個(gè)集合對(duì)象,包含了依賴屬性的名稱對(duì)應(yīng)的字符串蜈缤。

另外坞古,我們也可以實(shí)現(xiàn)一個(gè)命名為 keyPathsForValuesAffecting<Key> 的類方法來(lái)達(dá)到同樣的目的,其中<Key>是我們計(jì)算屬性的名稱劫樟。

注意:

  • 需要注意的就是當(dāng)我們重寫(xiě)+keyPathsForValuesAffectingValueForKey:時(shí)痪枫,需要去調(diào)用super的對(duì)應(yīng)方法,并返回一個(gè)包含父類中可能會(huì)對(duì)key指定屬性產(chǎn)生影響的屬性集合叠艳。
  • 重寫(xiě)方法是在目標(biāo)對(duì)象類里奶陈,而不是觀察者對(duì)象的類里,這里要注意附较。

實(shí)現(xiàn)如下:

@implementation Student

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{

    if ([key isEqualToString:@"resume"]) {

        return [NSSet setWithObjects:@"name",@"age", nil];

    }

    return [super keyPathsForValuesAffectingValueForKey:key];

}

//或者重寫(xiě)下面的方法
//+ (NSSet<NSString *> *)keyPathsForValuesAffectingResume{
//
//    return [NSSet setWithObjects:@"name",@"age", nil];
//
//}

@end
//ViewController.h
- (void)viewDidLoad {
    [super viewDidLoad];

    student = [[Student alloc] init];
    
    student.name = @"xds";
    student.age = 18;
    
    [student addObserver:self forKeyPath:@"resume" options:NSKeyValueObservingOptionNew context:nil];

    student.name = @"xdsxxxx";
    student.age = 22;

}

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

}

輸出如下吃粒,發(fā)現(xiàn)當(dāng)name和age改變時(shí),會(huì)通知resume屬性也被改變了拒课。

2018-08-29 11:26:56.125679+0800 KVO[1170:91206] {
    kind = 1;
    new = "name = xdsxxxx,age = 18";
}
2018-08-29 11:26:56.125984+0800 KVO[1170:91206] {
    kind = 1;
    new = "name = xdsxxxx,age = 22";
}
9徐勃、集合屬性的監(jiān)聽(tīng)
  1. 對(duì)于集合屬性(這里指NSArray和NSSet,不包括NSDictionary)的KVO早像,我們需要知道:對(duì)于集合屬性僻肖,只有在賦值時(shí)會(huì)觸發(fā)KVO,改變集合屬性里的元素是不會(huì)觸發(fā)KVO的(比如添加卢鹦、刪除臀脏、修改元素)

當(dāng)給集合對(duì)象賦值時(shí),是可以觸發(fā)KVO的揉稚。

//Student.h
@interface Student : NSObject
@property (copy, nonatomic) NSArray *classmates;
@end

- (void)viewDidLoad {

    [super viewDidLoad];

    student = [[Student alloc] init];
    
    student.name = @"xds";
    student.age = 18;
    
    [student addObserver:self forKeyPath:@"classmates" options:NSKeyValueObservingOptionNew context:nil];

    student.classmates = [NSArray array];

}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{

    NSLog(@"%@",change);

}
@end

//輸出
2018-08-29 11:57:31.909262+0800 KVO[1230:110669] {
    kind = 1;
    new =     (
    );
}
  1. 我們可以通過(guò)使用KVC的集合代理對(duì)象(collection proxy object)來(lái)處理集合相關(guān)的操作秒啦,使集合對(duì)象內(nèi)部元素改變時(shí)也能觸發(fā)KVO

直接操作:

有序集合對(duì)應(yīng)方法如下:

-countOf<Key>
//必須實(shí)現(xiàn)搀玖,對(duì)應(yīng)于NSArray的基本方法count:
-objectIn<Key>AtIndex:
-<key>AtIndexes:
//這兩個(gè)必須實(shí)現(xiàn)一個(gè)余境,對(duì)應(yīng)于 NSArray 的方法 objectAtIndex: 和 objectsAtIndexes:
-get<Key>:range:
//不是必須實(shí)現(xiàn)的,但實(shí)現(xiàn)后可以提高性能灌诅,其對(duì)應(yīng)于 NSArray 方法 getObjects:range:

-insertObject:in<Key>AtIndex:
-insert<Key>:atIndexes:
//兩個(gè)必須實(shí)現(xiàn)一個(gè)葛超,類似于 NSMutableArray 的方法 insertObject:atIndex: 和 insertObjects:atIndexes:
-removeObjectFrom<Key>AtIndex:
-remove<Key>AtIndexes:
//兩個(gè)必須實(shí)現(xiàn)一個(gè),類似于 NSMutableArray 的方法 removeObjectAtIndex: 和 removeObjectsAtIndexes:
-replaceObjectIn<Key>AtIndex:withObject:
-replace<Key>AtIndexes:with<Key>:
//可選的延塑,如果在此類操作上有性能問(wèn)題绣张,就需要考慮實(shí)現(xiàn)之

無(wú)序集合對(duì)應(yīng)方法如下:

-countOf<Key>
//必須實(shí)現(xiàn),對(duì)應(yīng)于NSArray的基本方法count:
-objectIn<Key>AtIndex:
-<key>AtIndexes:
//這兩個(gè)必須實(shí)現(xiàn)一個(gè)关带,對(duì)應(yīng)于 NSArray 的方法 objectAtIndex: 和 objectsAtIndexes:
-get<Key>:range:
//不是必須實(shí)現(xiàn)的侥涵,但實(shí)現(xiàn)后可以提高性能,其對(duì)應(yīng)于 NSArray 方法 getObjects:range:

-insertObject:in<Key>AtIndex:
-insert<Key>:atIndexes:
//兩個(gè)必須實(shí)現(xiàn)一個(gè)宋雏,類似于 NSMutableArray 的方法 insertObject:atIndex: 和 insertObjects:atIndexes:
-removeObjectFrom<Key>AtIndex:
-remove<Key>AtIndexes:
//兩個(gè)必須實(shí)現(xiàn)一個(gè)芜飘,類似于 NSMutableArray 的方法 removeObjectAtIndex: 和 removeObjectsAtIndexes:
-replaceObjectIn<Key>AtIndex:withObject:
-replace<Key>AtIndexes:with<Key>:
//這兩個(gè)都是可選的,如果在此類操作上有性能問(wèn)題磨总,就需要考慮實(shí)現(xiàn)之
  1. 在進(jìn)行可變集合對(duì)象操作時(shí)嗦明,先調(diào)用下面方法通過(guò)key或者keyPath獲取集合對(duì)象,然后再對(duì)集合對(duì)象進(jìn)行add或remove等操作時(shí)蚪燕,就會(huì)觸發(fā)KVO的消息通知了娶牌。這種方式屬于間接操作,是實(shí)際開(kāi)發(fā)中最常用到的馆纳。

key方法:

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key;
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;

keyPath方法:

- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath;
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
例子:
student = [[Student alloc] init];

student.name = @"xds";
student.age = 18;

[student addObserver:self forKeyPath:@"classmates" options:NSKeyValueObservingOptionNew context:nil];

student.classmates = [NSMutableArray array];

NSMutableArray *array = [student mutableArrayValueForKey:@"classmates"];

[array addObject:@"4"];

輸出:
2018-08-29 12:54:51.889282+0800 KVO[1404:146332] {
    kind = 1;
    new =     (
    );
}
2018-08-29 12:54:51.889866+0800 KVO[1404:146332] {
    indexes = "<_NSCachedIndexSet: 0x604000033f80>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
    kind = 2;
    new =     (
        4
    );
}

**通過(guò) - (NSMutableArray )mutableArrayValueForKey:(NSString )key; 這個(gè)方法诗良,我們便可以將可變數(shù)組與強(qiáng)大的KVO結(jié)合在一起。KVO機(jī)制能在集合改變的時(shí)候把詳細(xì)的變化放進(jìn)change字典中鲁驶。

  1. 如果我們想到手動(dòng)控制集合屬性消息的發(fā)送鉴裹,則可以使用下面幾個(gè)方法,即:
-willChange:valuesAtIndexes:forKey:
-didChange:valuesAtIndexes:forKey:
或
-willChangeValueForKey:withSetMutation:usingObjects:
-didChangeValueForKey:withSetMutation:usingObjects:

10钥弯、監(jiān)聽(tīng)信息

如果我們想獲取一個(gè)對(duì)象上有哪些觀察者正在監(jiān)聽(tīng)其屬性的修改径荔,則可以查看對(duì)象的observationInfo屬性,其聲明如下:

@property void *observationInfo

可以看到它是一個(gè)void類型指針(就是id類型)脆霎,指向一個(gè)包含所有觀察者的一個(gè)標(biāo)識(shí)信息對(duì)象总处,這些信息包含了每個(gè)監(jiān)聽(tīng)的觀察者,注冊(cè)時(shí)設(shè)定的選項(xiàng)等等绪穆。我們還是用示例來(lái)看看辨泳。

使用如下:

id info = student.observationInfo;

NSLog(@"%@", [info description]);

11虱岂、小結(jié)

KVO作為Objective-C中兩個(gè)對(duì)象間通信機(jī)制中的一種玖院,提供了一種非常強(qiáng)大的機(jī)制菠红。在經(jīng)典的MVC架構(gòu)中,控制器需要確保視圖與模型的同步难菌,當(dāng)model對(duì)象改變時(shí)试溯,視圖應(yīng)該隨之改變以反映模型的變化;當(dāng)用戶和控制器交互的時(shí)候郊酒,模型也應(yīng)該做出相應(yīng)的改變遇绞。而KVO便為我們提供了這樣一種同步機(jī)制:我們讓控制器去監(jiān)聽(tīng)一個(gè)model對(duì)象屬性的改變,并根據(jù)這種改變來(lái)更新我們的視圖燎窘。所有摹闽,有效地使用KVO,對(duì)我們應(yīng)用的開(kāi)發(fā)意義重大褐健。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末付鹿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蚜迅,更是在濱河造成了極大的恐慌舵匾,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谁不,死亡現(xiàn)場(chǎng)離奇詭異坐梯,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)刹帕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門吵血,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人偷溺,你說(shuō)我怎么就攤上這事践瓷。” “怎么了亡蓉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵晕翠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我砍濒,道長(zhǎng)淋肾,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任爸邢,我火速辦了婚禮樊卓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘杠河。我一直安慰自己碌尔,他們只是感情好浇辜,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著唾戚,像睡著了一般柳洋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叹坦,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天熊镣,我揣著相機(jī)與錄音,去河邊找鬼募书。 笑死绪囱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的莹捡。 我是一名探鬼主播鬼吵,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼篮赢!你這毒婦竟也來(lái)了齿椅?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤荷逞,失蹤者是張志新(化名)和其女友劉穎媒咳,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體种远,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涩澡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坠敷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妙同。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖膝迎,靈堂內(nèi)的尸體忽然破棺而出粥帚,到底是詐尸還是另有隱情,我是刑警寧澤限次,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布芒涡,位于F島的核電站,受9級(jí)特大地震影響卖漫,放射性物質(zhì)發(fā)生泄漏费尽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一羊始、第九天 我趴在偏房一處隱蔽的房頂上張望旱幼。 院中可真熱鬧,春花似錦突委、人聲如沸柏卤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)缘缚。三九已至勾笆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間忙灼,已是汗流浹背匠襟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工钝侠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留该园,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓帅韧,卻偏偏與公主長(zhǎng)得像里初,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子忽舟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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