今天和大家討論一下OC中KVC(KeyValueCoding)鍵值編碼
KVC定義
KVC(KeyValueCoding)鍵值編碼技術(shù)可以讓我們?cè)贠C的開發(fā)中使用字符串作為Key來訪問某個(gè)對(duì)象的屬性值或者為某個(gè)對(duì)象的屬性值進(jìn)行賦值。而不需要通過暴露在頭文件中的存取器方法進(jìn)行訪問和賦值操作狸吞。通過這種方法勉耀,我們可以獲取某個(gè)對(duì)象并沒有暴露在頭文件中的實(shí)例變量和為這個(gè)實(shí)例變量進(jìn)行賦值操作。通過這種方法可以繞過編譯器的審查蹋偏,使我們能夠做出一些”不太合規(guī)“的操作便斥,而不會(huì)讓編譯器報(bào)錯(cuò)。因此這種形式的存取是在運(yùn)行時(shí)進(jìn)行的威始,也是OC動(dòng)態(tài)特性的一種體現(xiàn)枢纠。
KVC原理
NSObject有個(gè)分類(NSKeyValueCoding)關(guān)于KVC的實(shí)現(xiàn)都在里面,所以黎棠,所有繼承自NSObject的對(duì)象都可以使用KVC晋渺,而結(jié)構(gòu)體和某些Swift對(duì)象
因?yàn)闆]有繼承NSObject而不可以使用
KVC取值設(shè)值以及查找順序
KVC進(jìn)行取值和設(shè)值操作時(shí)會(huì)根據(jù)你所傳遞的字符串按照一定規(guī)律進(jìn)行查找,我們編寫代碼進(jìn)行測(cè)試脓斩,以找到這個(gè)規(guī)律木西。
首先我們新建一個(gè)Person類
- 取值
在我們程序入口利用KVC來獲取Person示例的name屬性
Person *zhangSan = [Person new];
NSLog(@"%@",[zhangSan valueForKey:@"name"]);
在Person.m編寫代碼如下:
#import "Person.h"
@interface Person ()
{
NSString *_name;//第一個(gè)查找的實(shí)例變量
NSString *_isName;//第二個(gè)查找的實(shí)例變量
NSString *name;//第三個(gè)查找的實(shí)例變量
NSString *isName;//第四個(gè)查找的實(shí)例變量
}
@end
@implementation Person
- (instancetype)init
{
self = [super init];
if (self) {
_name = @"ivar _name";
_isName = @"ivar _isName";
name = @"ivar name";
isName = @"ivar isName";
}
return self;
}
//第一個(gè)取值查找的方法
-(NSString *)getName
{
return @"getName method";
}
//第二個(gè)取值查找的方法
-(NSString *)name
{
return @"name method";
}
//第三個(gè)取值查找的方法
-(NSString *)isName
{
return @"isName method";
}
//第四個(gè)取值查找的方法
-(NSString *)_name
{
return @"_name method";
}
//當(dāng)成數(shù)組處理
-(NSInteger)countOfName
{
return 2;
}
-(id)objectInNameAtIndex:(NSInteger)index
{
return @"數(shù)組成員";
}
//是否可以直接操作實(shí)例變量,默認(rèn)返回yes
+(BOOL)accessInstanceVariablesDirectly
{
NSLog(@"accessInstanceVariablesDirectly");
return YES;
}
@end
經(jīng)過代碼測(cè)試我們發(fā)現(xiàn)俭厚,當(dāng)我們對(duì)Person的示例使用KVC來獲取它的name屬性時(shí)户魏,系統(tǒng)先會(huì)按照順序查找一系列方法是否存在如果存在便調(diào)用并拿到這個(gè)方法的返回值作為kvc取值的結(jié)果,這一系列的方法的順序便是我上文代碼中注釋的順序挪挤,分別是getName name isName _name,如果第一個(gè)方法就存在那便不再調(diào)用之后的方法叼丑,以此類推。
當(dāng)四個(gè)方法全都沒有找到時(shí)扛门,我們的系統(tǒng)會(huì)把name當(dāng)做數(shù)組去處理鸠信,看看是否實(shí)現(xiàn)了代碼中的兩個(gè)方法。如果實(shí)現(xiàn)了论寨,就返回一個(gè)數(shù)組星立。
如果數(shù)組方法也沒有找到爽茴,會(huì)當(dāng)做集合去處理,在實(shí)現(xiàn)中查找相應(yīng)的方法绰垂。
如果集合的相關(guān)方法也沒有找到室奏,系統(tǒng)會(huì)調(diào)用accessInstanceVariablesDirectly方法秽之,這個(gè)方法是指示是否允許直接操作示例變量的泽本,如果你沒有重寫這個(gè)方法,默認(rèn)返回是yes吟逝。如果返回NO則程序不再往下執(zhí)行占业,崩潰绒怨,如果返回yes,那么將會(huì)按照順序查找相應(yīng)的示例變量谦疾,他們的順序是:_name _isName name isName;如果有對(duì)應(yīng)的實(shí)例變量那就返回他的值南蹂,如果四個(gè)都查找不到,那么程序崩潰念恍。
- 賦值
賦值的方式和取值的方式類似也是先按順序查找方法六剥,如果方法都沒有查找到,那么調(diào)用accessInstanceVariablesDirectly方法峰伙,如果返回yes則會(huì)按照同樣的順序查找相應(yīng)的實(shí)例變量
在程序入口處:
Person *zhangSan = [Person new];
[zhangSan setValue:@"2341" forKey:@"name"];
在Person.m編寫代碼如下:
//第一個(gè)查找的方法
-(void)setName:(NSString *)name
{
NSLog(@"setName %@",name);
}
//第二個(gè)查找的方法
-(void)_setName:(NSString *)name
{
NSLog(@"_setName %@",name);
}
keypath
在KVC中還可以使用keypath路徑來訪問屬性
舉例說明:就剛剛的Person類我們給它創(chuàng)建一個(gè)lover屬性仗考,lover的類型也是Person那么我們想要獲取某個(gè)Person對(duì)象的lover的name我們要怎么做呢?我們可以這樣
Person *zhangSan = [Person new];
[zhangSan valueForKeyPath:@"lover.name"];
KVC允許我們使用.來連接屬性這樣我們可以方便的獲取對(duì)象的屬性的屬性词爬。秃嗜。。
避免崩潰
正常情況下如果我們把nil設(shè)置給一個(gè)非對(duì)象屬性顿膨,或者查找一個(gè)并不存在的key程序就會(huì)崩潰锅锨,我們通過重寫下面的這些方法可以避免崩潰
-(void)setNilValueForKey:(NSString *)key
{
NSLog(@"不允許設(shè)置為nil");
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
NSLog(@"不存在這個(gè)key");
}
-(id)valueForUndefinedKey:(NSString *)key
{
NSLog(@"不存在這個(gè)key");
return nil;
}
數(shù)字和結(jié)構(gòu)體
當(dāng)屬性類型為數(shù)字或者結(jié)構(gòu)體時(shí),我們不能直接用數(shù)字和結(jié)構(gòu)體來賦值恋沃,而是需要轉(zhuǎn)化為NSNumber和NSValue來賦值必搞,同樣的,我們獲取到的值也會(huì)被轉(zhuǎn)化為NSNumber和NSValue類型囊咏。
數(shù)組的KVC應(yīng)用
在數(shù)組中KVC有一些特有的運(yùn)算符恕洲,我們一起看一下用法吧
我們編寫代碼如下:
Person *zhangsan = [Person new];
[zhangsan setValue:@10 forKey:@"age"];
Person *lisi = [Person new];
[lisi setValue:@20 forKey:@"age"];
Person *wangwu = [Person new];
[wangwu setValue:@30 forKey:@"age"];
Person *zhaoliu = [Person new];
[zhaoliu setValue:@10 forKey:@"age"];
NSArray *peoples = @[zhangsan,lisi,wangwu,zhaoliu];
NSLog(@"%ld",((NSNumber*)[peoples valueForKeyPath:@"@min.age"]).integerValue) ;
NSLog(@"%ld",((NSNumber*)[peoples valueForKeyPath:@"@max.age"]).integerValue) ;
NSLog(@"%ld",((NSNumber*)[peoples valueForKeyPath:@"@sum.age"]).integerValue) ;
NSLog(@"%ld",((NSNumber*)[peoples valueForKeyPath:@"@avg.age"]).integerValue) ;
NSLog(@"%ld",((NSNumber*)[peoples valueForKeyPath:@"@count"]).integerValue) ;
NSLog(@"%@",[peoples valueForKeyPath:@"@distinctUnionOfObjects.age"]);
NSLog(@"%@",[peoples valueForKeyPath:@"@unionOfObjects.age"]);
輸出結(jié)果為
2018-09-29 16:57:49.628811+0800 KvcKvoTest[254:3290335] 10
2018-09-29 16:57:49.628903+0800 KvcKvoTest[254:3290335] 30
2018-09-29 16:57:49.629141+0800 KvcKvoTest[254:3290335] 70
2018-09-29 16:57:49.629283+0800 KvcKvoTest[254:3290335] 17
2018-09-29 16:57:49.629370+0800 KvcKvoTest[254:3290335] 4
2018-09-29 16:57:49.629520+0800 KvcKvoTest[254:3290335] (
10,
20,
30
)
2018-09-29 16:57:49.629647+0800 KvcKvoTest[254:3290335] (
10,
20,
30,
10
)
前面的五種運(yùn)算符相信大家都可以明白它的用法,后面的兩個(gè)運(yùn)算符distinctUnionOfObjects為將數(shù)組中的對(duì)應(yīng)屬性排序去重返回一個(gè)數(shù)組梅割,unionOfObjects為將數(shù)組中的對(duì)應(yīng)屬性返回一個(gè)數(shù)組霜第。
字典的KVC應(yīng)用
字典也可以像普通對(duì)象一樣使用kvc,因此valueForKeyPath可以很方便的用來操作多層字典户辞。
還有兩個(gè)方法用來字典轉(zhuǎn)對(duì)象和對(duì)象轉(zhuǎn)字典:
- 對(duì)象轉(zhuǎn)字典:
Person *zhangsan = [Person new];
[zhangsan setValue:@10 forKey:@"age"];
[zhangsan setValue:@"zhangsan" forKey:@"name"];
NSDictionary *dic= [zhangsan dictionaryWithValuesForKeys:@[@"age",@"name"]];
NSLog(@"%@",dic);
- 字典轉(zhuǎn)對(duì)象:
Person *zhangsan = [Person new];
[zhangsan setValuesForKeysWithDictionary:@{@"name":@"zhangsan",@"age":@10}];
KVC用途總結(jié)
使用KVC可以動(dòng)態(tài)的取值泌类,賦值,可以操作私有變量底燎,可以修改一些系統(tǒng)控件刃榨,可以對(duì)數(shù)組進(jìn)行一些操作弹砚,以及對(duì)象和字典的轉(zhuǎn)換。
和所有OC的動(dòng)態(tài)特性一樣枢希,如果濫用會(huì)容易導(dǎo)致崩潰和一些不好排查的問題出現(xiàn)桌吃。