本文參考鏈接:
Foundation: NSKeyValueObserving(KVO)
概述
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è)步驟:
注冊(cè)O(shè)bserver晋涣;
接收通知。
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)。
注意:分清觀察者對(duì)象和目標(biāo)對(duì)象拯坟,調(diào)用 addObserver:forKeyPath:options:context: 方法的對(duì)象是目標(biāo)對(duì)象但金,observer是觀察者對(duì)象,keyPath是目標(biāo)對(duì)象的屬性郁季。
注意冷溃,在注冊(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做的處理是直接讓程序崩潰工坊。
- 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的配置確定饮睬。
注意:options參數(shù)可以配置多個(gè),如:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld篮奄,使用 | 或運(yùn)算符連接捆愁。
調(diào)用addObserver:forKeyPath:options:context:方法時(shí),觀察者對(duì)象與被觀察屬性所屬的對(duì)象都不會(huì)被retain窟却,也就是說(shuō)昼丑,引用計(jì)數(shù)不會(huì)加1。
可以重復(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)容。
- 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惊搏。
- 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ì)介紹一下原理,如下圖:
- 當(dāng)某個(gè)類的對(duì)象第一次被觀察時(shí)森爽,系統(tǒng)就會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類恨豁,在這個(gè)派生類中重寫(xiě)****基類中任何**被觀察屬性的 **setter 方法****。
- 派生類在被重寫(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 的凡涩。
- 同時(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)
可以通過(guò) KVO 在 Model 和 Controller 之間進(jìn)行通信堂鲜。
在類的內(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";
但是可以通過(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操作读拆。
調(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))
-
觸發(fā) KVO 的三種方式:
- 如果使用了 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: 方法夏志。
- 直接使用了訪問(wèn)器方法(點(diǎn)語(yǔ)法),會(huì)在運(yùn)行時(shí)重寫(xiě) setter 方法,調(diào)用 will/didChangeValueForKey: 方法沟蔑;
- 顯示調(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)
- 對(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 = (
);
}
- 我們可以通過(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)之
- 在進(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字典中鲁驶。
- 如果我們想到手動(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ā)意義重大褐健。