在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

iOS开发中KVC、KVO简介_import

1 #import <Foundation/Foundation.h>2 3 @interface Dog : NSObject4 5 @property (nonatomic, copy) NSString *name;6 7 @end

iOS开发中KVC、KVO简介_import

Dog.m

1 #import "Dog.h"2 3 @implementation Dog4 5 @end

Person.h

iOS开发中KVC、KVO简介_import

 1 #import <Foundation/Foundation.h> 2  3 @class Dog; 4  5 @interface Person : NSObject 6  7 @property (nonatomic, copy) NSString *name; 8 @property (nonatomic, copy) NSString *sex; 9 10 @property (nonatomic, strong) Dog *dog;11 12 @end

iOS开发中KVC、KVO简介_import

Person.m

1 #import "Person.h"2 3 @implementation Person4 5 @end

KVC的使用:

iOS开发中KVC、KVO简介_import

 1 Person *person = [[Person alloc] init]; 2     //kvc  设置值 3     [person setValue:@"Jim" forKey:@"name"]; 4     [person setValue:@"boy" forKey:@"sex"]; 5      6     //kvc 获得值 7     NSLog(@"name = %@  sex = %@",[person valueForKey:@"name"],[person valueForKey:@"sex"]); 8      9     Dog *dog = [[Dog alloc] init];10     person.dog = dog;11     //kvc  设置复杂路径值12     [person setValue:@"Teddy" forKeyPath:@"dog.name"];13     //kvc  获得复杂路径值14     NSLog(@"dog name = %@",[person valueForKeyPath:@"dog.name"]);

iOS开发中KVC、KVO简介_import

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

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

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

二:KVC中 setValuesForKeysWithDictionary: 的使用

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

使用 setValuesForKeysWithDictionary:dict 的一个例子:

student.h

 

iOS开发中KVC、KVO简介_import

 1 #import <Foundation/Foundation.h> 2  3 @interface Student : NSObject 4  5 /** 6  *  学号 7  */ 8 @property (nonatomic, copy) NSString *num; 9 /**10  *  姓名11  */12 @property (nonatomic, copy) NSString *name;13 /**14  *  身高15  */16 @property (nonatomic, assign) float height;17 18 /**19  *  初始化的两个方法20  *21  */22 - (instancetype)initWithDict:(NSDictionary *)dict;23 + (instancetype)stuWithDict:(NSDictionary *)dict;24 25 27 28 @end

iOS开发中KVC、KVO简介_import

 

student.m

iOS开发中KVC、KVO简介_import

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

iOS开发中KVC、KVO简介_import

用一个字典初始化对象

iOS开发中KVC、KVO简介_import

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

iOS开发中KVC、KVO简介_import

setValuesForKeyWithDictionary:dict  的原理

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

iOS开发中KVC、KVO简介_import

1 - (void) setProperty:(NSDictionary *)dict2 {3     for(NSString *key in [dict allKeys])4     {5         NSString *value = [dict objectForKey:key];6         [self setValue:value forKey:key];7     }8 }

iOS开发中KVC、KVO简介_import

调用时:

iOS开发中KVC、KVO简介_import

1 - (instancetype)initWithDict:(NSDictionary *)dict{2     if(self = [super init]){3         //[self setValuesForKeysWithDictionary:dict];4         [self setProperty:dict];5     }6     return self;7 }

iOS开发中KVC、KVO简介_import

和setValuesForKeyWithDictionary:dict 功能是一样的。

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

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

iOS开发中KVC、KVO简介_import

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

iOS开发中KVC、KVO简介_import

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

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

student.h中添加代码:

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

student.m中添加代码:

1 - (void)setValue:(id)value forUndefinedKey:(NSString *)key2 {3     4 }

当出现没有属性的情况时,就会调用 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

 

iOS开发中KVC、KVO简介_import

1 #import <Foundation/Foundation.h>2 3 @interface Dog : NSObject4 5 @property (nonatomic, copy) NSString *name;6 7 @end

iOS开发中KVC、KVO简介_import

 

Dog.m

1 #import "Dog.h"2 3 @implementation Dog4 5 @end

People.h

iOS开发中KVC、KVO简介_import

 1 #import <Foundation/Foundation.h> 2  3 @class Dog; 4  5 @interface People : NSObject 6  7 @property (nonatomic , copy) NSString *name; 8 @property (nonatomic , strong) Dog *dog; 9 10 @end

iOS开发中KVC、KVO简介_import

People.m

iOS开发中KVC、KVO简介_import

 1 #import "People.h" 2 #import "Dog.h" 3  4 @implementation People 5  6 /** 7  *  初始化时增加对dog的监听 8  * 9  */10 - (void)setDog:(Dog *)dog11 {12     _dog = dog;13     [self.dog addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];14 }15 /**16  *  重写observeValueForKeyPath方法17  */18 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context19 {20     if([keyPath isEqualToString:@"name"]){21         NSLog(@"newName = %@",[change objectForKey:@"new"]);22     }23 }24 25 - (void)dealloc26 {27     //移除监听28     [self.dog removeObserver:self forKeyPath:@"name"];29 }30 31 @end

iOS开发中KVC、KVO简介_import

测试KVO函数:

iOS开发中KVC、KVO简介_import

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

iOS开发中KVC、KVO简介_import

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

observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context 方法。