今天和大家讨论一下OC中KVC(KeyValueCoding)键值编码
KVC定义
KVC(KeyValueCoding)键值编码技术可以让我们在OC的开发中使用字符串作为Key来访问某个对象的属性值或者为某个对象的属性值进行赋值。而不需要通过暴露在头文件中的存取器方法进行访问和赋值操作。通过这种方法,我们可以获取某个对象并没有暴露在头文件中的实例变量和为这个实例变量进行赋值操作。通过这种方法可以绕过编译器的审查,使我们能够做出一些”不太合规“的操作,而不会让编译器报错。因此这种形式的存取是在运行时进行的,也是OC动态特性的一种体现。
KVC原理
NSObject有个分类(NSKeyValueCoding)关于KVC的实现都在里面,所以,所有继承自NSObject的对象都可以使用KVC,而结构体和某些Swift对象
因为没有继承NSObject而不可以使用
KVC取值设值以及查找顺序
KVC进行取值和设值操作时会根据你所传递的字符串按照一定规律进行查找,我们编写代码进行测试,以找到这个规律。
首先我们新建一个Person类
- 取值
在我们程序入口利用KVC来获取Person示例的name属性
Person *zhangSan = [Person new];
NSLog(@"%@",[zhangSan valueForKey:@"name"]);
在Person.m编写代码如下:
#import "Person.h"
@interface Person ()
{
NSString *_name;//第一个查找的实例变量
NSString *_isName;//第二个查找的实例变量
NSString *name;//第三个查找的实例变量
NSString *isName;//第四个查找的实例变量
}
@end
@implementation Person
- (instancetype)init
{
self = [super init];
if (self) {
_name = @"ivar _name";
_isName = @"ivar _isName";
name = @"ivar name";
isName = @"ivar isName";
}
return self;
}
//第一个取值查找的方法
-(NSString *)getName
{
return @"getName method";
}
//第二个取值查找的方法
-(NSString *)name
{
return @"name method";
}
//第三个取值查找的方法
-(NSString *)isName
{
return @"isName method";
}
//第四个取值查找的方法
-(NSString *)_name
{
return @"_name method";
}
//当成数组处理
-(NSInteger)countOfName
{
return 2;
}
-(id)objectInNameAtIndex:(NSInteger)index
{
return @"数组成员";
}
//是否可以直接操作实例变量,默认返回yes
+(BOOL)accessInstanceVariablesDirectly
{
NSLog(@"accessInstanceVariablesDirectly");
return YES;
}
@end
经过代码测试我们发现,当我们对Person的示例使用KVC来获取它的name属性时,系统先会按照顺序查找一系列方法是否存在如果存在便调用并拿到这个方法的返回值作为kvc取值的结果,这一系列的方法的顺序便是我上文代码中注释的顺序,分别是getName name isName _name,如果第一个方法就存在那便不再调用之后的方法,以此类推。
当四个方法全都没有找到时,我们的系统会把name当做数组去处理,看看是否实现了代码中的两个方法。如果实现了,就返回一个数组。
如果数组方法也没有找到,会当做集合去处理,在实现中查找相应的方法。
如果集合的相关方法也没有找到,系统会调用accessInstanceVariablesDirectly方法,这个方法是指示是否允许直接操作示例变量的,如果你没有重写这个方法,默认返回是yes。如果返回NO则程序不再往下执行,崩溃,如果返回yes,那么将会按照顺序查找相应的示例变量,他们的顺序是:_name _isName name isName;如果有对应的实例变量那就返回他的值,如果四个都查找不到,那么程序崩溃。
- 赋值
赋值的方式和取值的方式类似也是先按顺序查找方法,如果方法都没有查找到,那么调用accessInstanceVariablesDirectly方法,如果返回yes则会按照同样的顺序查找相应的实例变量
在程序入口处:
Person *zhangSan = [Person new];
[zhangSan setValue:@"2341" forKey:@"name"];
在Person.m编写代码如下:
//第一个查找的方法
-(void)setName:(NSString *)name
{
NSLog(@"setName %@",name);
}
//第二个查找的方法
-(void)_setName:(NSString *)name
{
NSLog(@"_setName %@",name);
}
keypath
在KVC中还可以使用keypath路径来访问属性
举例说明:就刚刚的Person类我们给它创建一个lover属性,lover的类型也是Person那么我们想要获取某个Person对象的lover的name我们要怎么做呢?我们可以这样
Person *zhangSan = [Person new];
[zhangSan valueForKeyPath:@"lover.name"];
KVC允许我们使用.来连接属性这样我们可以方便的获取对象的属性的属性。。。
避免崩溃
正常情况下如果我们把nil设置给一个非对象属性,或者查找一个并不存在的key程序就会崩溃,我们通过重写下面的这些方法可以避免崩溃
-(void)setNilValueForKey:(NSString *)key
{
NSLog(@"不允许设置为nil");
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
NSLog(@"不存在这个key");
}
-(id)valueForUndefinedKey:(NSString *)key
{
NSLog(@"不存在这个key");
return nil;
}
数字和结构体
当属性类型为数字或者结构体时,我们不能直接用数字和结构体来赋值,而是需要转化为NSNumber和NSValue来赋值,同样的,我们获取到的值也会被转化为NSNumber和NSValue类型。
数组的KVC应用
在数组中KVC有一些特有的运算符,我们一起看一下用法吧
我们编写代码如下:
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"]);
输出结果为
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
)
前面的五种运算符相信大家都可以明白它的用法,后面的两个运算符distinctUnionOfObjects为将数组中的对应属性排序去重返回一个数组,unionOfObjects为将数组中的对应属性返回一个数组。
字典的KVC应用
字典也可以像普通对象一样使用kvc,因此valueForKeyPath可以很方便的用来操作多层字典。
还有两个方法用来字典转对象和对象转字典:
- 对象转字典:
Person *zhangsan = [Person new];
[zhangsan setValue:@10 forKey:@"age"];
[zhangsan setValue:@"zhangsan" forKey:@"name"];
NSDictionary *dic= [zhangsan dictionaryWithValuesForKeys:@[@"age",@"name"]];
NSLog(@"%@",dic);
- 字典转对象:
Person *zhangsan = [Person new];
[zhangsan setValuesForKeysWithDictionary:@{@"name":@"zhangsan",@"age":@10}];
KVC用途总结
使用KVC可以动态的取值,赋值,可以操作私有变量,可以修改一些系统控件,可以对数组进行一些操作,以及对象和字典的转换。
和所有OC的动态特性一样,如果滥用会容易导致崩溃和一些不好排查的问题出现。
网友评论