在iOS开发中,KVC和KVO是经常被用到的。可以使用KVC对对象的属性赋值和取得对象的属性值,可以使用KVO监听对象属性值的变化。简单介绍一下KVC和KVO。

一:键值编码(KVC)

KVC,全称 Key Value Coding(键值编码),是OC 语言的一个特性,使用KVC,可以对对象的属性进行动态读写。

KVC的操作方法由 NSKeyValueCoding协议提供,而NSObject已经实现了这个协议,因此OC中的几乎所有对象都可以使用KVC操作。常用的KVC操作方法有:

(1)设置属性值

  setValue:value forKey:key (用于简单路径,也就是直接属性)

  setValue: value forKeyPath:key (用于复杂路径,也就是间接属性)

(2)获取属性值

  valueForKey: key (用于获取简单路径的属性值)

  valueForKeyPath: key (用于获取复杂路径的属性值)

通过代码来看KVC的具体使用:

两个类分别是Dog类和Person类

Dog.h

1#import<Foundation/Foundation.h>23@interfaceDog:NSObject45@property(nonatomic,copy)NSString*name;67@end

Dog.m

1#import"Dog.h"23@implementationDog45@end

Person.h

1#import<Foundation/Foundation.h>23@classDog;45@interfacePerson:NSObject67@property(nonatomic,copy)NSString*name;8@property(nonatomic,copy)NSString*sex;910@property(nonatomic,strong)Dog*dog;1112@end

Person.m

1#import"Person.h"23@implementationPerson45@end

KVC的使用:

1Person*person=[[Personalloc]init];2//kvc设置值3[personsetValue:@"Jim"forKey:@"name"];4[personsetValue:@"boy"forKey:@"sex"];56//kvc获得值7NSLog(@"name=%@sex=%@",[personvalueForKey:@"name"],[personvalueForKey:@"sex"]);89Dog*dog=[[Dogalloc]init];10person.dog=dog;11//kvc设置复杂路径值12[personsetValue:@"Teddy"forKeyPath:@"dog.name"];13//kvc获得复杂路径值14NSLog(@"dogname=%@",[personvalueForKeyPath:@"dog.name"]);

可以看到,KVC使用对应的函数即可设置值、获得值。那么,KVC是如何查找一个属性进行读取呢?KVC遵循下面的规则,假设要对name属性进行读取:

(1)设置属性:优先考虑setName方法;如果没有,则搜索成员变量_name;如果没找到,则搜索成员变量name;如果还没有找到,则调用setValue: forUndefineKey:方法。

(2)读取属性:优先考虑getName方法;如果没有,则搜索成员变量_name;如果没找到,则搜索成员变量name;如果还没有找到,则调用valueForUndefineKey:方法。

二:KVC中 setValuesForKeysWithDictionary: 的使用

KVC中有一个非常重要的方法: setValuesForKeysWithDictionary:dict ,该方法可以将一个字典映射到一个对象,省去了给对象一一赋值的步骤。

使用 setValuesForKeysWithDictionary:dict 的一个例子:

student.h

1#import<Foundation/Foundation.h>23@interfaceStudent:NSObject45/**6*学号7*/8@property(nonatomic,copy)NSString*num;9/**10*姓名11*/12@property(nonatomic,copy)NSString*name;13/**14*身高15*/16@property(nonatomic,assign)floatheight;1718/**19*初始化的两个方法20*21*/22-(instancetype)initWithDict:(NSDictionary*)dict;23+(instancetype)stuWithDict:(NSDictionary*)dict;24252728@end

student.m

-(instancetype)initWithDict:(NSDictionary*(self=+(instancetype)stuWithDict:(NSDictionary*

用一个字典初始化对象

1-(void)initStudent2{3NSDictionary*dict=[NSDictionarydictionaryWithObjectsAndKeys:4@"Tom",@"name",5@"110",@"num",6@"170.0",@"height",7nil];8Student*stu=[[Studentalloc]initWithDict:dict];9NSLog(@"name=%@num=%@height=%f",stu.name,stu.num,stu.height);10}

setValuesForKeyWithDictionary:dict 的原理

  实际上,setValuesForKeyWithDictionary:dict 方法就是遍历dict,对dict 中的每个键值调用 setValue: forKey: 方法。可以用下面的方法模拟setValuesForKeyWithDictionary:dict:

1-(void)setProperty:(NSDictionary*)dict2{3for(NSString*keyin[dictallKeys])4{5NSString*value=[dictobjectForKey:key];6[selfsetValue:valueforKey:key];7}8}

调用时:

1-(instancetype)initWithDict:(NSDictionary*)dict{2if(self=[superinit]){3//[selfsetValuesForKeysWithDictionary:dict];4[selfsetProperty:dict];5}6returnself;7}

和setValuesForKeyWithDictionary:dict 功能是一样的。

使用setValuesForKeyWithDictionary:dict一个需要注意的地方

当字典中有某个值,而对象没有相应的属性时,会发生崩溃。比如,新的字典如下:

1-(void)initStudent2{3NSDictionary*dict=[NSDictionarydictionaryWithObjectsAndKeys:4@"Tom",@"name",5@"110",@"num",6@"170.0",@"height",7@"boy",@"sex",nil];8Student*stu=[[Studentalloc]initWithDict:dict];9NSLog(@"name=%@num=%@height=%f",stu.name,stu.num,stu.height);10}

字典中有 sex属性,但是Studeng对象中没有这个属性,此时会发生崩溃。解决方法:

实现 setValue: forUndefineKey: 方法,在该方法中处理出现没有属性的情况。

student.h中添加代码:

1-(void)setValue:(id)valueforUndefinedKey:(NSString*)key;

student.m中添加代码:

1-(void)setValue:(id)valueforUndefinedKey:(NSString*)key2{34}

当出现没有属性的情况时,就会调用 setValue: forUndefineKey: 方法。因为该方法没做处理,所以这种情况不做处理,不会发生崩溃。需要注意的是:setValue: forUndefineKey: 方法用途很广泛,比如说字典中某个key值 为id,但是在OC中id 是关键字,这种情况也可以在setValue: forUndefineKey: 方法中处理。

三:键值监听(KVO)

KVO全称 Key Value Observing。使用KVO可以实现视图组件和数据模型的分离,视图作为监听器,当模型的属性值发生变化后,监听器可以做相应的处理。KVO的方法由NSKeyValueObserving协议提供,同样NSObject已经实现了该协议,因此几乎所有的对象都可以使用KVO。

使用KVO操作常用的方法如下:

注册制定路径的监听器: addObserver: forKeyPath: option: context:

删除制定路径的监听器:removeObserver: forKeyPath:

触发监听时的方法:observeValueForKeyPath: ofObject: change: context:

一个KVO的例子:

有两个类: Dog类和People类

Dog.h

1#import<Foundation/Foundation.h>23@interfaceDog:NSObject45@property(nonatomic,copy)NSString*name;67@end

Dog.m

1#import"Dog.h"23@implementationDog45@end

People.h

1#import<Foundation/Foundation.h>23@classDog;45@interfacePeople:NSObject67@property(nonatomic,copy)NSString*name;8@property(nonatomic,strong)Dog*dog;910@end

People.m

1#import"People.h"2#import"Dog.h"34@implementationPeople56/**7*初始化时增加对dog的监听8*9*/10-(void)setDog:(Dog*)dog11{12_dog=dog;13[self.dogaddObserver:selfforKeyPath:@"name"options:NSKeyValueObservingOptionNewcontext:nil];14}15/**16*重写observeValueForKeyPath方法17*/18-(void)observeValueForKeyPath:(NSString*)keyPathofObject:(id)objectchange:(NSDictionary<NSString*,id>*)changecontext:(void*)context19{20if([keyPathisEqualToString:@"name"]){21NSLog(@"newName=%@",[changeobjectForKey:@"new"]);22}23}2425-(void)dealloc26{27//移除监听28[self.dogremoveObserver:selfforKeyPath:@"name"];29}3031@end

测试KVO函数:

1-(void)testKVO2{3People*people=[[Peoplealloc]init];4people.name=@"Tom";5Dog*dog=[[Dogalloc]init];6dog.name=@"Teddy";7people.dog=dog;89//执行到这一步后,会触发监听器的函数(observeValueForKeyPath)10dog.name=@"testChange";11}

在代码中,当修改dog 的name属性时,就会触发监听方法,也就是

observeValueForKeyPath:(NSString*)keyPathofObject:(id)objectchange:(NSDictionary<NSString*,id>*)changecontext:(void*)context方法。