iOS KVO和KVC詳解

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)潔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末敷扫,一起剝皮案震驚了整個(gè)濱河市哀蘑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌葵第,老刑警劉巖绘迁,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異卒密,居然都是意外死亡缀台,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)哮奇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)膛腐,“玉大人,你說(shuō)我怎么就攤上這事屏镊∫捞郏” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵而芥,是天一觀的道長(zhǎng)律罢。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么误辑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任沧踏,我火速辦了婚禮,結(jié)果婚禮上巾钉,老公的妹妹穿的比我還像新娘翘狱。我一直安慰自己,他們只是感情好砰苍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布潦匈。 她就那樣靜靜地躺著,像睡著了一般赚导。 火紅的嫁衣襯著肌膚如雪茬缩。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天吼旧,我揣著相機(jī)與錄音凰锡,去河邊找鬼。 笑死圈暗,一個(gè)胖子當(dāng)著我的面吹牛掂为,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播员串,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼勇哗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了昵济?” 一聲冷哼從身側(cè)響起智绸,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎访忿,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體斯稳,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡海铆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挣惰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卧斟。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖憎茂,靈堂內(nèi)的尸體忽然破棺而出珍语,到底是詐尸還是另有隱情,我是刑警寧澤竖幔,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布板乙,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏募逞。R本人自食惡果不足惜蛋铆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望放接。 院中可真熱鬧刺啦,春花似錦、人聲如沸纠脾。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)苟蹈。三九已至糊渊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汉操,已是汗流浹背再来。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留磷瘤,地道東北人芒篷。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像采缚,于是被迫代替她去往敵國(guó)和親针炉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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

  • KVC(Key-valuecoding)鍵值編碼扳抽,單看這個(gè)名字可能不太好理解篡帕。其實(shí)翻譯一下就很簡(jiǎn)單了,就是指iOS...
    榕樹(shù)頭閱讀 693評(píng)論 0 2
  • KVC(Key-value coding)鍵值編碼贸呢,單看這個(gè)名字可能不太好理解镰烧。其實(shí)翻譯一下就很簡(jiǎn)單了,就是指iO...
    我的夢(mèng)工廠閱讀 889評(píng)論 1 8
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,089評(píng)論 1 32
  • 1. KVO 一.KVO原理的使用與證明 我們?cè)陂_(kāi)發(fā)的過(guò)程中經(jīng)常使用KVO和KVC,但是我們并不了解其底層原理和功...
    周灬閱讀 835評(píng)論 0 9
  • 夜闌風(fēng)靜縠紋平 小舟從此逝 江海寄余生
    追憶者閱讀 267評(píng)論 4 3