IOS中Key-Value Coding (KVC)俗称键值编码,是一个非正式的协议,它提供一种机制来间接访问对象的属性。
直接访问对象是通过调用访问器的方法实现,而kvc不需要调用访问器的设置和获取方法,可以直接访问对象的属性。
KVC的操作方法由NSKeyValueCoding协议提供,而NSObject就实现了这个协议,也就是说ObjC中几乎所有的对象都支持KVC操作,
常用的KVC操作方法如下:
1)对属性赋值(动态设置):
setValue:属性值 frKey:属性名(用于简单路径);
setValue:属性值 forKeyPath:属性路径(用于复合路径,例如Person有一个Account类型的属性,那么person.account就是一个复合属性);kvc的一般用法:
setValue:属性值 forKey:属性名和valueForKey:属性名
- (void)viewDidLoad {
[super viewDidLoad];
[self test1];
}
#pragma mark --setValue:属性值 forKey:属性名
-(void)test1{
Person * person = [[Person alloc]init];
[person setValue:@"一个屌丝的程序员" forKey:@"name"];
NSString * name =[person valueForKeyPath:@"name"];
NSLog(@"%@",name);
}
输出结果:
2)对属性取值(动态读取):
valueForKey:属性名 (用于简单路径);
valueForKeyPath:属性名(用于复合路径);
- (void)viewDidLoad {
[super viewDidLoad];
[self test2];
}
#pragma mark --setValue:属性值 forKeyPath:属性路径和valueForKeyPath:属性名(用于复合路径)
-(void)test2{
Person * person = [[Person alloc]init];
person.course = [[Course alloc]init];
[person setValue:@"Course是一个自定义类,course是student的属性" forKeyPath:@"course.CourseName"];
NSString * CourseName = [person valueForKeyPath:@"course.CourseName"];
NSLog(@"CourseName:%@",CourseName);
}
输出结果:
注意:
(1). key的值必须正确,如果拼写错误,会出现异常
1> * 首先去接收者(调用方法的那个对象)的类中查找与key相匹配的访问器方法(-set<Key>),如果找到了一个方法,就检查它参数的类型,如果它的参数类型不是一个对象指针类型,但是只为nil,就会执行setNilValueForKey:方法,setNilValueForKey:方法的默认实现,是产生一个NSInvalidArgumentException的异常,但是你可以重写这个方法.
* 如果方法参数的类是一个对象指针类型,就会简单的执行这个方法,传入对应的参数.如果方法的参数类型是NSNumber或NSValue的对应的基本类型,先把它转换为基本数据类,再执行方法,传入转换后的数据.
2> * 如果没有对应的访问器方法(setter方法),如果接受者的类的+accessInstanceVariablesDirectly方法返回YES,那么就查找这个接受者的与key相匹配的实例变量(匹配模式为_<key>,_is<Key>,<key>,is<Key>):比如:key为age,只要属性存在_age,_isAge,age,isAge中的其中一个就认为匹配上了,如果找到这样的一个实例变量,并且的类型是一个对象指针类型,
3> * 首先released对象上的旧值,然后把传入的新值retain后的传入的值赋值该成员变量,如果方法的参数类型是NSNumber或NSValue的对应的基本类型,先把它转换为基本数据类,再执行方法,传入转换后的数据.
* 如果访问器方法和实例变量都没有找到,执行setValue:forUndefinedKey:方法,该方法的默认实现是产生一个NSUndefinedKeyException 类型的异常,但是我们可以重写setValue:forUndefinedKey:方法
(2). 当key的值是没有定义的,valueForUndefinedKey:这个方法会被调用,如果你自己写了这个方法,key的值出错就会调用到这里来
(3). 因为类key反复嵌套,所以有个keyPath的概念,keyPath就是用.号来把一个一个key链接起来,这样就可以根据这个路径访问下去
(4). NSArray/NSSet等都支持KVC
3)key值找不到时,异常处理
使用kvc时,如果代码中的key值不存在,会抛出异常,可以在类中通过重写它提供
- (void)setValue:(id)value forUndefinedKey:(NSString *)key</span>
当key不存在时,会自动调用上面的这个方法,可以在这个方法中进行处理- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
if([key isEqualToString:@"id"])
self.userid = value;
}
4)KVC的字典转模型和模型转字典
1>字典转模型
当我们需要把json字符串反序列化成我们想要的对象 ,可以通过下面的方法
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues
- (id)initWithDictionary:(NSDictionary *)dictionary {
self = [self init];
if (self){
[self setValuesForKeysWithDictionary:dictionary];
}
return self;
}
注意:字典中的key 和对象属性要求一样
2>模型转字典
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
NSDictionary *dic = [self.dataArray dictionaryWithValuesForKeys:@[@"name",@"age"]];
for (int i =0 ; i<dic.count; i++) {
NSLog(@"%@", [dic objectForKey:[dic allKeys][i]]);
}
5)总结
1)KVC需要把字符串属性名称转换后才能赋值,调用的set方法,性能消耗较高
2)KVC查找一个属性进行读取的一般顺序
1>动态设置属性,
优先考虑调用setA方法,如果没有该方法则优先考虑搜索成员变量_a,如果仍然不存在则搜索成员变量a,如果最后仍然没搜索到则会调用这个类的setValue:forUndefinedKey:方法(注意搜索过程中不管这些方法、成员变量是私有的还是公共的都能正确设置);
2>动态读取属性
优先考虑调用a方法(属性a的getter方法),如果没有搜索到则会优先搜索成员变量_a,如果仍然不存在则搜索成员变量a,如果最后仍然没搜索到则会调用这个类的valueforUndefinedKey:方法(注意搜索过程中不管这些方法、成员变量是私有的还是公共的都能正确读取);
3>解析的数据 类型不确定处理
-(void)setValue:(id)value forKey:(NSString *)key{
if ([key isEqualToString:@"itemCode"]) {
self.itemCode =[NSString stringWithFormat:@"%@",value];
}else{
[super setValue:value forKey:key];
}
}
别忘加上这个,也要重写(未被定义的不做处理):
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{ }