KVC
KVC定義
KVC(Key-value coding)鍵值編碼蕉朵,就是指iOS的開(kāi)發(fā)中秸脱,可以允許開(kāi)發(fā)者通過(guò)Key名直接訪問(wèn)對(duì)象的屬性帆啃,或者給對(duì)象的屬性賦值鱼喉。而不需要調(diào)用明確的存取方法。這樣就可以在運(yùn)行時(shí)動(dòng)態(tài)地訪問(wèn)和修改對(duì)象的屬性模她。而不是在編譯時(shí)確定稻艰,這也是iOS開(kāi)發(fā)中的黑魔法之一。很多高級(jí)的iOS開(kāi)發(fā)技巧都是基于KVC實(shí)現(xiàn)的侈净。
在實(shí)現(xiàn)了訪問(wèn)器方法的類中尊勿,使用點(diǎn)語(yǔ)法和KVC訪問(wèn)對(duì)象其實(shí)差別不大,二者可以任意混用畜侦。但是沒(méi)有訪問(wèn)起方法的類中元扔,點(diǎn)語(yǔ)法無(wú)法使用,這時(shí)KVC就有優(yōu)勢(shì)了旋膳。
1.設(shè)置器和訪問(wèn)器的定義
*?? 給單一實(shí)例變量賦值的方法叫做設(shè)置器.(setter方法)
*?? 給單一實(shí)例變量值的方法叫做訪問(wèn)器.(getter方法)
KVC的定義都是對(duì)NSObject的擴(kuò)展來(lái)實(shí)現(xiàn)的澎语,Objective-C中有個(gè)顯式的NSKeyValueCoding類別名,所以對(duì)于所有繼承了NSObject的類型验懊,都能使用KVC(一些純Swift類和結(jié)構(gòu)體是不支持KVC的擅羞,因?yàn)闆](méi)有繼承NSObject),下面是KVC最為重要的四個(gè)方法:
- (nullable id)valueForKey:(NSString *)key; //直接通過(guò)Key來(lái)取值
- (void)setValue:(nullable id)value forKey:(NSString *)key;? ? ? ? ? //通過(guò)Key來(lái)設(shè)值
- (nullable id)valueForKeyPath:(NSString *)keyPath;? ? ? ? ? ? ? ? ? //通過(guò)KeyPath來(lái)取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;? //通過(guò)KeyPath來(lái)設(shè)值
KVC設(shè)值
KVC要設(shè)值义图,那么就要對(duì)象中對(duì)應(yīng)的key减俏,KVC在內(nèi)部是按什么樣的順序來(lái)尋找key的。當(dāng)調(diào)用setValue:屬性值 forKey:@”name“的代碼時(shí)碱工,底層的執(zhí)行機(jī)制如下:
程序優(yōu)先調(diào)用set:屬性值方法娃承,代碼通過(guò)setter方法完成設(shè)置奏夫。注意,這里的是指成員變量名草慧,首字母大小寫(xiě)要符合KVC的命名規(guī)則桶蛔,下同
如果沒(méi)有找到setName:方法匙头,KVC機(jī)制會(huì)檢查+ (BOOL)accessInstanceVariablesDirectly方法有沒(méi)有返回YES漫谷,默認(rèn)該方法會(huì)返回YES,如果你重寫(xiě)了該方法讓其返回NO的話蹂析,那么在這一步KVC會(huì)執(zhí)行setValue:forUndefinedKey:方法舔示,不過(guò)一般開(kāi)發(fā)者不會(huì)這么做。所以KVC機(jī)制會(huì)搜索該類里面有沒(méi)有名為_(kāi)的成員變量电抚,無(wú)論該變量是在類接口處定義惕稻,還是在類實(shí)現(xiàn)處定義,也無(wú)論用了什么樣的訪問(wèn)修飾符蝙叛,只在存在以_命名的變量顽冶,KVC都可以對(duì)該成員變量賦值纱昧。
如果該類即沒(méi)有set:方法,也沒(méi)有_成員變量,KVC機(jī)制會(huì)搜索_is的成員變量圾亏。
和上面一樣,如果該類即沒(méi)有set:方法陷揪,也沒(méi)有_和_is成員變量王滤,KVC機(jī)制再會(huì)繼續(xù)搜索和is的成員變量。再給它們賦值际起。
如果上面列出的方法或者成員變量都不存在拾碌,系統(tǒng)將會(huì)執(zhí)行該對(duì)象的setValue:forUndefinedKey:方法,默認(rèn)是拋出異常街望。
簡(jiǎn)單來(lái)說(shuō)就是如果沒(méi)有找到Set<Key>方法的話校翔,會(huì)按照_key,_iskey灾前,key防症,iskey的順序搜索成員并進(jìn)行賦值操作。
如果開(kāi)發(fā)者想讓這個(gè)類禁用KVC里豫柬,那么重寫(xiě)+ (BOOL)accessInstanceVariablesDirectly方法讓其返回NO即可告希,這樣的話如果KVC沒(méi)有找到set:屬性名時(shí),會(huì)直接用setValue:forUndefinedKey:方法烧给。
#import <Foundation/Foundation.h>
@interface Test: NSObject {
? ? NSString *_name;
}
@end
@implementation Test
@end
int main(int argc, const char * argv[]) {
? ? @autoreleasepool {
? ? ? ? // insert code here...
? ? ? ? //生成對(duì)象
? ? ? ? Test *obj = [[Test alloc] init];
? ? ? ? //通過(guò)KVC賦值name
? ? ? ? [obj setValue:@"xiaoming" forKey:@"name"];
? ? ? ? //通過(guò)KVC取值name打印
? ? ? ? NSLog(@"obj的名字是%@", [obj valueForKey:@"name"]);
? ? }
? ? return 0;
}
打印結(jié)果: 2018-05-05 15:36:52.354405+0800 KVCKVO[35231:6116188] obj的名字是xiaoming
可以看到通過(guò)- (void)setValue:(nullable id)value forKey:(NSString *)key;和- (nullable id)valueForKey:(NSString *)key;成功設(shè)置和取出obj對(duì)象的name值燕偶。
再看一下設(shè)置accessInstanceVariablesDirectly為NO的效果:
#import <Foundation/Foundation.h>
@interface Test: NSObject {
? ? NSString *_name;
}
@end
@implementation Test
+ (BOOL)accessInstanceVariablesDirectly {
? ? return NO;
}
- (id)valueForUndefinedKey:(NSString *)key {
? ? NSLog(@"出現(xiàn)異常,該key不存在%@",key);
? ? return nil;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
? ? NSLog(@"出現(xiàn)異常础嫡,該key不存在%@", key);
}
@end
int main(int argc, const char * argv[]) {
? ? @autoreleasepool {
? ? ? ? // insert code here...
? ? ? ? //生成對(duì)象
? ? ? ? Test *obj = [[Test alloc] init];
? ? ? ? //通過(guò)KVC賦值name
? ? ? ? [obj setValue:@"xiaoming" forKey:@"name"];
? ? ? ? //通過(guò)KVC取值name打印
? ? ? ? NSLog(@"obj的名字是%@", [obj valueForKey:@"name"]);
? ? }
? ? return 0;
}
打印結(jié)果:
2018-05-05 15:45:22.399021+0800 KVCKVO[35290:6145826] 出現(xiàn)異常指么,該key不存在name
2018-05-05 15:45:22.399546+0800 KVCKVO[35290:6145826] 出現(xiàn)異常酝惧,該key不存在name
2018-05-05 15:45:22.399577+0800 KVCKVO[35290:6145826] obj的名字是(null)
可以看到accessInstanceVariablesDirectly為NO的時(shí)候KVC只會(huì)查詢setter和getter這一層,下面尋找key的相關(guān)變量執(zhí)行就會(huì)停止伯诬,直接報(bào)錯(cuò)晚唇。
設(shè)置accessInstanceVariablesDirectly為YES,再修改_name為_(kāi)isName盗似,看看執(zhí)行是否成功哩陕。
#import <Foundation/Foundation.h>
@interface Test: NSObject {
? ? NSString *_isName;
}
@end
@implementation Test
+ (BOOL)accessInstanceVariablesDirectly {
? ? return YES;
}
- (id)valueForUndefinedKey:(NSString *)key {
? ? NSLog(@"出現(xiàn)異常,該key不存在%@",key);
? ? return nil;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
? ? NSLog(@"出現(xiàn)異常赫舒,該key不存在%@", key);
}
@end
int main(int argc, const char * argv[]) {
? ? @autoreleasepool {
? ? ? ? // insert code here...
? ? ? ? //生成對(duì)象
? ? ? ? Test *obj = [[Test alloc] init];
? ? ? ? //通過(guò)KVC賦值name
? ? ? ? [obj setValue:@"xiaoming" forKey:@"name"];
? ? ? ? //通過(guò)KVC取值name打印
? ? ? ? NSLog(@"obj的名字是%@", [obj valueForKey:@"name"]);
? ? }
? ? return 0;
}
打印結(jié)果:
2018-05-05 15:49:53.444350+0800 KVCKVO[35303:6157671] obj的名字是xiaoming
從打印可以看到設(shè)置accessInstanceVariablesDirectly為YES悍及,KVC會(huì)繼續(xù)按照順序查找,并成功設(shè)值和取值了接癌。
當(dāng)調(diào)用valueForKey:@”name“的代碼時(shí)心赶,KVC對(duì)key的搜索方式不同于setValue:屬性值 forKey:@”name“,其搜索方式如下:
首先按get,,is的順序方法查找getter方法缺猛,找到的話會(huì)直接調(diào)用缨叫。如果是BOOL或者Int等值類型, 會(huì)將其包裝成一個(gè)NSNumber對(duì)象荔燎。
KVC處理異常
KVC中最常見(jiàn)的異常就是不小心使用了錯(cuò)誤的key耻姥,或者在設(shè)值中不小心傳遞了nil的值,KVC中有專門(mén)的方法來(lái)處理這些異常湖雹。
KVC處理nil異常
通常情況下咏闪,KVC不允許你要在調(diào)用setValue:屬性值 forKey:(或者keyPath)時(shí)對(duì)非對(duì)象傳遞一個(gè)nil的值。很簡(jiǎn)單摔吏,因?yàn)橹殿愋褪遣荒転閚il的鸽嫂。如果你不小心傳了,KVC會(huì)調(diào)用setNilValueForKey:方法征讲。這個(gè)方法默認(rèn)是拋出異常据某,所以一般而言最好還是重寫(xiě)這個(gè)方法。
#import <Foundation/Foundation.h>
@interface Test: NSObject {
? ? NSUInteger age;
}
@end
@implementation Test
- (void)setNilValueForKey:(NSString *)key {
? ? NSLog(@"不能將%@設(shè)成nil", key);
}
@end
int main(int argc, const char * argv[]) {
? ? @autoreleasepool {
? ? ? ? // insert code here...
? ? ? ? //Test生成對(duì)象
? ? ? ? Test *test = [[Test alloc] init];
? ? ? ? //通過(guò)KVC設(shè)值test的age
? ? ? ? [test setValue:nil forKey:@"age"];
? ? ? ? //通過(guò)KVC取值age打印
? ? ? ? NSLog(@"test的年齡是%@", [test valueForKey:@"age"]);
? ? }
? ? return 0;
}
KVC處理UndefinedKey異常
通常情況下诗箍,KVC不允許你要在調(diào)用setValue:屬性值 forKey:(或者keyPath)時(shí)對(duì)不存在的key進(jìn)行操作癣籽。
不然,會(huì)報(bào)錯(cuò)forUndefinedKey發(fā)生崩潰滤祖,重寫(xiě)forUndefinedKey方法避免崩潰筷狼。
#import <Foundation/Foundation.h>
@interface Test: NSObject {
}
@end
@implementation Test
- (id)valueForUndefinedKey:(NSString *)key {
? ? NSLog(@"出現(xiàn)異常,該key不存在%@",key);
? ? return nil;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
? ? NSLog(@"出現(xiàn)異常匠童,該key不存在%@", key);
}
@end
int main(int argc, const char * argv[]) {
? ? @autoreleasepool {
? ? ? ? // insert code here...
? ? ? ? //Test生成對(duì)象
? ? ? ? Test *test = [[Test alloc] init];
? ? ? ? //通過(guò)KVC設(shè)值test的age
? ? ? ? [test setValue:@10 forKey:@"age"];
? ? ? ? //通過(guò)KVC取值age打印
? ? ? ? NSLog(@"test的年齡是%@", [test valueForKey:@"age"]);
? ? }
? ? return 0;
}
打印結(jié)果:
2018-05-05 16:30:18.564680+0800 KVCKVO[35487:6277523] 出現(xiàn)異常埂材,該key不存在age
2018-05-05 16:30:18.565190+0800 KVCKVO[35487:6277523] 出現(xiàn)異常,該key不存在age
2018-05-05 16:30:18.565216+0800 KVCKVO[35487:6277523] test的年齡是(null)
KVC處理數(shù)值和結(jié)構(gòu)體類型屬性
不是每一個(gè)方法都返回對(duì)象汤求,但是valueForKey:總是返回一個(gè)id對(duì)象俏险,如果原本的變量類型是值類型或者結(jié)構(gòu)體严拒,返回值會(huì)封裝成NSNumber或者NSValue對(duì)象。
這兩個(gè)類會(huì)處理從數(shù)字竖独,布爾值到指針和結(jié)構(gòu)體任何類型裤唠。然后開(kāi)以者需要手動(dòng)轉(zhuǎn)換成原來(lái)的類型。
盡管valueForKey:會(huì)自動(dòng)將值類型封裝成對(duì)象莹痢,但是setValue:forKey:卻不行种蘸。你必須手動(dòng)將值類型轉(zhuǎn)換成NSNumber或者NSValue類型,才能傳遞過(guò)去格二。
因?yàn)閭鬟f進(jìn)去和取出來(lái)的都是id類型劈彪,所以需要開(kāi)發(fā)者自己擔(dān)保類型的正確性竣蹦,運(yùn)行時(shí)Objective-C在發(fā)送消息的會(huì)檢查類型顶猜,如果錯(cuò)誤會(huì)直接拋出異常。
舉個(gè)例子痘括,Person類有個(gè)NSInteger類型的age屬性长窄,如下:
// Person.m
#import "Person.h"
@interface Person ()
@property (nonatomic,assign) NSInteger age;
@end
@implementation Person
@end
修改值
我們通過(guò)KVC技術(shù)使用如下方式設(shè)置age屬性的值:
[person setValue:[NSNumber numberWithInteger:5] forKey:@"age"];
我們賦給age的是一個(gè)NSNumber對(duì)象,KVC會(huì)自動(dòng)的將NSNumber對(duì)象轉(zhuǎn)換成NSInteger對(duì)象纲菌,然后再調(diào)用相應(yīng)的訪問(wèn)器方法設(shè)置age的值挠日。
獲取值
同樣,以如下方式獲取age屬性值:
[person valueForKey:@"age"];
這時(shí)翰舌,會(huì)以NSNumber的形式返回age的值嚣潜。
需要注意的是我們不能直接將一個(gè)數(shù)值通過(guò)KVC賦值的,我們需要把數(shù)據(jù)轉(zhuǎn)為NSNumber和NSValue類型傳入椅贱,那到底哪些類型數(shù)據(jù)要用NSNumber封裝哪些類型數(shù)據(jù)要用NSValue封裝呢懂算?看下面這些方法的參數(shù)類型就知道了:
可以使用NSNumber的數(shù)據(jù)類型有:就是一些常見(jiàn)的數(shù)值型數(shù)據(jù)。
可以使用NSValue的數(shù)據(jù)類型有:NSValue主要用于處理結(jié)構(gòu)體型的數(shù)據(jù)庇麦,它本身提供了如上集中結(jié)構(gòu)的支持计技。任何結(jié)構(gòu)體都是可以轉(zhuǎn)化成NSValue對(duì)象的,包括其它自定義的結(jié)構(gòu)體山橄。
KVC使用
動(dòng)態(tài)地取值和設(shè)值
利用KVC動(dòng)態(tài)的取值和設(shè)值是最基本的用途了垮媒。
用KVC來(lái)訪問(wèn)和修改私有變量
對(duì)于類里的私有屬性,Objective-C是無(wú)法直接訪問(wèn)的航棱,但是KVC是可以的睡雇。
Model和字典轉(zhuǎn)換
這是KVC強(qiáng)大作用的又一次體現(xiàn),KVC和Objc的runtime組合可以很容易的實(shí)現(xiàn)Model和字典的轉(zhuǎn)換饮醇。
修改一些控件的內(nèi)部屬性
這也是iOS開(kāi)發(fā)中必不可少的小技巧它抱。眾所周知很多UI控件都由很多內(nèi)部UI控件組合而成的,但是Apple度沒(méi)有提供這訪問(wèn)這些控件的API驳阎,這樣我們就無(wú)法正常地訪問(wèn)和修改這些控件的樣式抗愁。
而KVC在大多數(shù)情況可下可以解決這個(gè)問(wèn)題馁蒂。最常用的就是個(gè)性化UITextField中的placeHolderText了。
KVO
KVO定義
KVO 即 Key-Value Observing蜘腌,翻譯成鍵值觀察沫屡。它是一種觀察者模式的衍生。其基本思想是撮珠,對(duì)目標(biāo)對(duì)象的某屬性添加觀察沮脖,當(dāng)該屬性發(fā)生變化時(shí),通過(guò)觸發(fā)觀察者對(duì)象實(shí)現(xiàn)的KVO接口方法芯急,來(lái)自動(dòng)的通知觀察者勺届。
觀察者模式是什么
一個(gè)目標(biāo)對(duì)象管理所有依賴于它的觀察者對(duì)象,并在它自身的狀態(tài)改變時(shí)主動(dòng)通知觀察者對(duì)象娶耍。這個(gè)主動(dòng)通知通常是通過(guò)調(diào)用各觀察者對(duì)象所提供的接口方法來(lái)實(shí)現(xiàn)的免姿。觀察者模式較完美地將目標(biāo)對(duì)象與觀察者對(duì)象解耦。
簡(jiǎn)單來(lái)說(shuō)KVO可以通過(guò)監(jiān)聽(tīng)key榕酒,來(lái)獲得value的變化胚膊,用來(lái)在對(duì)象之間監(jiān)聽(tīng)狀態(tài)變化。KVO的定義都是對(duì)NSObject的擴(kuò)展來(lái)實(shí)現(xiàn)的想鹰,Objective-C中有個(gè)顯式的NSKeyValueObserving類別名紊婉,所以對(duì)于所有繼承了NSObject的類型,都能使用KVO(一些純Swift類和結(jié)構(gòu)體是不支持KVC的辑舷,因?yàn)闆](méi)有繼承NSObject)喻犁。
KVO使用
注冊(cè)與解除注冊(cè)
如果我們已經(jīng)有了包含可供鍵值觀察屬性的類,那么就可以通過(guò)在該類的對(duì)象(被觀察對(duì)象)上調(diào)用名為 NSKeyValueObserverRegistration 的 category 方法將觀察者對(duì)象與被觀察者對(duì)象注冊(cè)與解除注冊(cè):
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
observer:觀察者何缓,也就是KVO通知的訂閱者肢础。訂閱著必須實(shí)現(xiàn)
observeValueForKeyPath:ofObject:change:context:方法
keyPath:描述將要觀察的屬性,相對(duì)于被觀察者歌殃。
options:KVO的一些屬性配置乔妈;有四個(gè)選項(xiàng)。
context: 上下文氓皱,這個(gè)會(huì)傳遞到訂閱著的函數(shù)中路召,用來(lái)區(qū)分消息,所以應(yīng)當(dāng)是不同的波材。
options所包括的內(nèi)容
NSKeyValueObservingOptionNew:change字典包括改變后的值
NSKeyValueObservingOptionOld:change字典包括改變前的值
NSKeyValueObservingOptionInitial:注冊(cè)后立刻觸發(fā)KVO通知
NSKeyValueObservingOptionPrior:值改變前是否也要通知(這個(gè)key決定了是否在改變前改變后通知兩次)
這兩個(gè)方法在手動(dòng)實(shí)現(xiàn)鍵值觀察時(shí)會(huì)用到股淡。注意在不用的時(shí)候,不要忘記解除注冊(cè)廷区,否則會(huì)導(dǎo)致內(nèi)存泄露唯灵。
處理變更通知
每當(dāng)監(jiān)聽(tīng)的keyPath發(fā)生變化了,就會(huì)在這個(gè)函數(shù)中回調(diào)隙轻。
- (void)observeValueForKeyPath:(NSString *)keyPath
? ? ? ? ? ? ? ? ? ? ? ofObject:(id)object
? ? ? ? ? ? ? ? ? ? ? ? change:(NSDictionary *)change
? ? ? ? ? ? ? ? ? ? ? context:(void *)context
手動(dòng)KVO(禁用KVO)
KVO的實(shí)現(xiàn)埠帕,是對(duì)注冊(cè)的keyPath中自動(dòng)實(shí)現(xiàn)了兩個(gè)函數(shù)垢揩,在setter中,自動(dòng)調(diào)用敛瓷。
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
鍵值觀察依賴鍵
有時(shí)候一個(gè)屬性的值依賴于另一對(duì)象中的一個(gè)或多個(gè)屬性叁巨,如果這些屬性中任一屬性的值發(fā)生變更,被依賴的屬性值也應(yīng)當(dāng)為其變更進(jìn)行標(biāo)記呐籽。因此锋勺,object 引入了依賴鍵。
KVO和線程
一個(gè)需要注意的地方是狡蝶,KVO 行為是同步的庶橱,并且發(fā)生與所觀察的值發(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ā)生:
KVO的實(shí)現(xiàn)依賴于Runtime的強(qiáng)大動(dòng)態(tài)能力订咸。
即當(dāng)一個(gè)類型為 ObjectA 的對(duì)象曼尊,被添加了觀察后,系統(tǒng)會(huì)生成一個(gè) NSKVONotifying_ObjectA 類脏嚷,并將對(duì)象的isa指針指向新的類骆撇,也就是說(shuō)這個(gè)對(duì)象的類型發(fā)生了變化。這個(gè)類相比較于ObjectA父叙,會(huì)重寫(xiě)以下幾個(gè)方法神郊。
重寫(xiě)setter
在 setter 中,會(huì)添加以下兩個(gè)方法的調(diào)用趾唱。
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
然后在?didChangeValueForKey:?中涌乳,去調(diào)用:
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
? ? ? ? ? ? ? ? ? ? ? ofObject:(nullable id)object
? ? ? ? ? ? ? ? ? ? ? ? change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
? ? ? ? ? ? ? ? ? ? ? context:(nullable void *)context;
包含了新值和舊值的通知。
于是實(shí)現(xiàn)了屬性值修改的通知甜癞。因?yàn)?KVO 的原理是修改 setter 方法夕晓,因此使用 KVO 必須調(diào)用 setter 。若直接訪問(wèn)屬性對(duì)象則沒(méi)有效果悠咱。
注:在我們?nèi)粘i_(kāi)發(fā)中蒸辆,我們一般取這個(gè)Options值為NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld征炼,這時(shí),當(dāng)變量值被修改時(shí)躬贡,我們?cè)赾hange字典中既能得到新值柒室,也能得到修改之前的值
PS:使用時(shí),addObserver與removeObserver需要成對(duì)出現(xiàn)逗宜,在銷毀對(duì)象時(shí)移除監(jiān)聽(tīng)雄右。
#import "QiCompany.h"
@interface QiCompany()
@property (nonatomic, strong) NSString *addr;
@end
@implementation QiCompany
- (void)aboutKVO {
? ? self.staff = [[QiStaff alloc] init];
? ? self.staff.staffId = @"1000119";
? ? self.staff.staffName = @"佩奇";
? ? [self.staff addObserver:self forKeyPath:@"staffId" options:NSKeyValueObservingOptionNew context:nil];
? ? [self.staff addObserver:self forKeyPath:@"staffName" options:NSKeyValueObservingOptionNew context:nil];
? ? self.staff.staffId = @"1000120";
? ? self.staff.staffName = @"佩德羅";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
? ? NSLog(@"keyPath = %@? object=%@? newValue=%@? context=%@", keyPath, object, [change objectForKey:@"new"], context);
}
- (void)dealloc {
? ? [self.staff removeObserver:self forKeyPath:@"staffId"];
? ? [self.staff removeObserver:self forKeyPath:@"staffName"];
}
@end
日志輸出:
2018-11-13 16:25:21.215872+0800 QiKVO&KVC[20986:895803] keyPath = staffId object=<QiStaff: 0x6000017286c0> newValue=1000120 context=(null)
2018-11-13 16:25:21.216040+0800 QiKVO&KVC[20986:895803] keyPath = staffName object=<QiStaff: 0x6000017286c0> newValue=佩德羅 context=(null)
總結(jié)
KVO 的本質(zhì)就是監(jiān)聽(tīng)對(duì)象的屬性進(jìn)行賦值的時(shí)候有沒(méi)有調(diào)用 setter 方法
系統(tǒng)會(huì)動(dòng)態(tài)創(chuàng)建一個(gè)繼承于?Person?的?NSKVONotifying_Person
person?的?isa?指針指向的類?Person?變成?NSKVONotifying_Person,所以接下來(lái)的?person.age?=?newAge?的時(shí)候纺讲,他調(diào)用的不是?Person?的?setter?方法擂仍,而是?NSKVONotifying_Person(子類)的?setter?方法
重寫(xiě)NSKVONotifying_Person的setter方法:[super setName:newName]
通知觀察者告訴屬性改變。
KVO給我們提供了更少的代碼熬甚,和比NSNotification好處逢渔,不需要修改被觀察的class, 永遠(yuǎn)都是觀察你的人做事情。 但是KVO也有些毛病乡括, 1. 如果沒(méi)有observer監(jiān)聽(tīng)key path, removeObsever:forKeyPath:context: 這個(gè)key path, 就會(huì)crash, 不像NSNotificationCenter removeObserver肃廓。 2. 對(duì)代碼你很難發(fā)現(xiàn)誰(shuí)監(jiān)聽(tīng)你的property的改動(dòng),查找起來(lái)比較麻煩诲泌。 3. 對(duì)于一個(gè)復(fù)雜和相關(guān)性很高的class盲赊,最好還是不要用KVO, 就用delegate 或者 notification的方式比較簡(jiǎn)潔。