什么是KVC
KVC鍵值編碼,允許通過key名直接訪問對(duì)象的屬性或者給對(duì)象的屬性賦值,而不需要調(diào)用對(duì)象的存取方法。這樣就可以在運(yùn)行時(shí)動(dòng)態(tài)的訪問和修改對(duì)象的屬性,而不是在編譯期間確定的。
KVC使用場(chǎng)景
動(dòng)態(tài)取值和設(shè)值
對(duì)于類里的私有屬性,Objective-C是無法直接訪問的,但是KVC是可以的。
Model和字典的相互轉(zhuǎn)換
KVCClass *class = [[KVCClass alloc]init];
NSDictionary *dict = @{@"name" : @"lixiao",@"gender" : @"male",@"home" : @"123",@"age" : @"20"};
//字典轉(zhuǎn)模型
[class setValuesForKeysWithDictionary:dict];
NSLog(@"%@",class.name);
NSArray *keyArray = @[@"name",@"gender",@"home",@"age"];
//模型轉(zhuǎn)字典
NSDictionary *getDict = [class dictionaryWithValuesForKeys:keyArray];
NSLog(@"%@",getDict);
這是KVC強(qiáng)大作用的又一體現(xiàn),字典轉(zhuǎn)模型可通過KVC提供的函數(shù)setValuesForKeysWithDictionary實(shí)現(xiàn),模型轉(zhuǎn)字典通過dictionaryWithValuesForKeys實(shí)現(xiàn)
修改私有變量、屬性
對(duì)于一個(gè)類的私有屬性,在其他類中是無法直接訪問的,但是kvc可以幫你做到
@interface KVCClass()
@property (nonatomic,copy) NSString *country;
@end
我在KVCClass中定義了一個(gè)私有屬性country
KVCClass *class = [[KVCClass alloc]init];
[class setValue:@"China" forKey:@"country"];
NSString *countryStr = [class valueForKey:@"country"];
NSLog(@"%@",countryStr);
在另一個(gè)類中創(chuàng)建KVCClass對(duì)象,使用KVC給私有屬性country賦值China
打印結(jié)果為
2019-05-27 19:08:18.402134+0800 Objective-C Test[54803:3049821] China
用KVC實(shí)現(xiàn)高階消息傳遞
當(dāng)對(duì)容器類使用KVC時(shí),valueForKey:將會(huì)被傳遞給容器中的每一個(gè)對(duì)象,而不是容器本身進(jìn)行操作。結(jié)果會(huì)被添加進(jìn)返回的容器中,這樣,開發(fā)者可以很方便的操作集合來返回另一個(gè)集合。
NSArray *provinceArray = @[@"henan",@"hebei",@"guizhou"];
NSArray *capProvinceArray = [provinceArray valueForKey:@"capitalizedString"];
NSLog(@"%@-%@",provinceArray,capProvinceArray);
上面代碼意思為當(dāng)你有一個(gè)provinceArray數(shù)組,里面的省份首字母都為小寫,需求是要將每個(gè)省份的首字母大寫,如果沒有KVC那么解決方案就是for循環(huán)遍歷每一個(gè)元素然后變?yōu)榇髮懀獽VC將capitalizedString方法傳遞給provinceArray數(shù)組的每一個(gè)元素,從打印結(jié)果來看每一項(xiàng)都被轉(zhuǎn)換為了大寫。
KVO
KVO即鍵值觀察,它是一種觀察者模式的衍生,基本思想是對(duì)目標(biāo)對(duì)象的某屬性添加觀察,當(dāng)該屬性發(fā)生變化時(shí),通過觸發(fā)觀察者對(duì)象實(shí)現(xiàn)的KVO接口方法,來自動(dòng)的通知觀察者。簡(jiǎn)單的來說就是監(jiān)聽key來獲得value的變化。
KVOController *kvoVC = [[KVOController alloc]init];
[kvoVC addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[self.navigationController pushViewController:kvoVC animated:YES];
self.name = @"hahahah";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.name = @"NiceForMe";
});
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"new is %@ old is %@",change[@"new"],change[@"old"]);
}
}
上面代碼中,創(chuàng)建了一個(gè)KVOController對(duì)象并給kvcVC添加監(jiān)聽對(duì)象以及監(jiān)聽屬性,自KVOController的.m文件中定義了私有屬性name并在初始化時(shí)給name屬性賦值,延時(shí)兩秒給name重新賦值,在observeValueForKeyPath方法中可以看到賦值后的打印結(jié)果為
2019-05-27 20:19:13.266064+0800 Objective-C Test[55405:3087272] new is NiceForMe old is hahahah
KVO原理
比如我創(chuàng)建了一個(gè)繼承與NSObject的類叫KVOClass,.h文件中定義了一個(gè)屬性叫name,我創(chuàng)建了一個(gè)實(shí)例KVOClass的實(shí)例對(duì)象kvoClass,執(zhí)行kvoClass addObserver的操作,使用runtime提供的函數(shù)輸出這個(gè)實(shí)例對(duì)象的isa指針指向的類(object_getClass)他所指向的類被替換成了NSKVONotifying_KVOClass,然后再輸出他的父類(class_getSuperClass(object_getClass))發(fā)現(xiàn)他的父類變成了KVOClass,再輸出setName:方法的地址(class_getMethodImplementation(object_getClass(kvoClass), @selector(setName:)))發(fā)現(xiàn)setName方法的地址也變了,輸出完畢后我執(zhí)行removeObserver的操作再輸出以上三個(gè)信息發(fā)現(xiàn)這個(gè)實(shí)例對(duì)象的isa指針又重新指向了KVOClass,父類變成了NSObject,setName方法地址也變了。
這說明當(dāng)我執(zhí)行addObserver操作的時(shí)候系統(tǒng)自動(dòng)幫我執(zhí)行了以下操作:
a.自動(dòng)生成了一個(gè)NSKVONotifying_+類名的這么一個(gè)類,并將這個(gè)實(shí)例對(duì)象的isa指針指向了這個(gè)新生成的類,同時(shí)將KVOClass作為這個(gè)新生成類的父類。
b.因?yàn)閟etName方法地址變了,說明重寫了監(jiān)聽屬性的set方法
c.當(dāng)我執(zhí)行remove操作的時(shí)候?qū)⑦@個(gè)新生成的類刪掉同時(shí)將isa指針指向原來的類
如何實(shí)現(xiàn)KVO
a.首先將keypath比如這里是name,轉(zhuǎn)換成setName:,使用runtime提供的方法class_getInstanceMethod獲得KVOClass里的setName方法。
b.然后執(zhí)行NSStringFromClass([self class])獲得這個(gè)類的類名,生成一個(gè)新的類名,格式為L(zhǎng)XKVO_+類名,判斷如果這個(gè)新的類名不存在那么就動(dòng)態(tài)生成這個(gè)類,使用函數(shù)objc_allocateClassPair和objc_registerClassPair
c.第三步將setName方法添加到這個(gè)類里面去,使用class_addMethod,最后使用object_setClass將這個(gè)類添加到系統(tǒng)中去