KVC和KVO这两个词看起来很唬人的样子,很多人一开始接触的时候都不知道这是什么鬼,而且掌握了后又容易忘。所以在这里简单做个笔记,以备不时之需。下面分别介绍一下KVC和KVO的使用方法。
1.KVC
KVC是Key-Value Coding的缩写,是键值编码的意思,它是一种可以直接通过字符串的名字(key)来访问类属性(实例变量)的机制。主要、、常用的是setValue:forKey:以及setValue:forKeyPath:这两个方法。
(1)先来看下setValue:forKey:的用法。建一个测试类Person,头文件里有个年龄属性age。
1 #import <Foundation/Foundation.h>
2
3 @interface Person : NSObject
4
5 @property (nonatomic, assign) NSInteger age;
6
7 @end
示例1: 在基类里我们可以直接通过setter方法或者setValue:forKey:方法对Person里的age成员赋值。
1 //setter方法赋值
2 self.person.age = 18;
3 NSLog(@"我-%ld,是通过setter方法赋予年龄的",self.person.age);
4
5 //setValue:forKey:方法赋值,第一个参数是要设置的值,第二个参数是变量名
6 [self.person setValue:@(24) forKey:@"age"];
7 NSLog(@"我-%ld,是通过setValueForKey方法赋予年龄的",self.person.age);
运行结果如下:
1 2016-03-14 10:07:15.135 KVC[815:66605] 我-18,是通过setter方法赋予年龄的
2 2016-03-14 10:07:15.135 KVC[815:66605] 我-24,是通过setValueForKey方法赋予年龄的
示例2:我们也可通过getter方法或者valueForKey:方法来获取变量的值.
1 self.person.age = 30;
2 NSLog(@"我-%ld,是通过setter方法获取年龄的",self.person.age);
3 NSLog(@"我-%@,是通过setter方法获取年龄的",[self.person valueForKey:@"age"]);
运行结果如下:
1 2016-03-14 10:18:20.098 KVC[869:133820] 我-30,是通过setter方法获取年龄的
2 2016-03-14 10:18:20.099 KVC[869:133820] 我-30,是通过valueForKey:方法获取年龄的
示例3:对于Person里的私有成员属性,我们只能通过setValue:forKey:和valueForKey:方法来进行赋值和获取。
譬如在Person.m文件里声明一个name属性。
1 @implementation Person
2 {
3 NSString *_name;
4 }
5
6 -(instancetype)init{
7
8 self = [super init];
9 if(self){
10
11 _name = @"小明";
12 }
13
14 return self;
15 }
然后我们对其进行赋值或获值。
1 //先通过valueForKey:方法获取name的值
2 NSLog(@"我原名叫-%@",[self.person valueForKey:@"name"]);
3 //再通过setValue:forKey:方法对其赋值
4 [self.person setValue:@"小王" forKey:@"name"];
5 NSLog(@"我现在叫-%@,是通过setValue:forKey:方法重命名的",[self.person valueForKey:@"name"]);
运行结果如下:
1 2016-03-14 10:36:55.067 KVC[941:197346] 我原名叫-小明
2 2016-03-14 10:36:55.067 KVC[941:197346] 我现在叫-小王,是通过setValue:forKey:方法重命名的
(2)再来看下setValue:forKeyPath:的用法。这是通过键路径来给变量赋值,可以简单理解为给类的成员的成员赋值。
譬如说,我们在上面的基础上再建一个类Address,里面有一个address的成员属性,然后在Preson里面声明。
1 #import <Foundation/Foundation.h>
2
3 @interface Address : NSObject
4
5 @property (nonatomic, strong) NSString *address;
6
7 @end
8
9
10 #import <Foundation/Foundation.h>
11 @class Address;
12
13 @interface Person : NSObject
14
15 @property (nonatomic, assign) NSInteger age;
16 @property (nonatomic, strong) Address *addr;
17
18 @end
示例1.给Person的Address成员里的address属性进行赋值。
1 //通过setter方法赋值
2 self.person.addr.address = @"老家";
3 NSLog(@"我之前在-%@,是通过setter方法得到地址的",self.person.add.address);
4 //通过setValue:forKeyPath:进行赋值
5 [self.person setValue:@"深圳" forKeyPath:@"addr.address"];
6 NSLog(@"我现在在-%@,是通过setValue:forKeyPath:方法得到地址的",self.person.addr.address);
运行结果如下:
1 2016-03-14 10:56:59.127 KVC[1046:317724] 我之前在-老家,是通过setter方法得到地址的 2 2016-03-14 10:56:59.128 KVC[1046:317724] 我现在在-深圳,是通过setValue:forKeyPath:方法得到地址的
示例2:通过getter或者valueForKeyPath:获取address的值。
1 self.person.addr.address = @"北京";
2 NSLog(@"我将要去-%@,通过getter方法得知目的地的",self.person.addr.address);
3 NSLog(@"我将要去-%@,通过valueForKeyPath:方法得知目的地的",[self.person valueForKeyPath:@"addr.address"]);
运行结果如下:
1 2016-03-14 11:04:27.547 KVC[1102:348596] 我将要去-北京,通过getter方法得知目的地的
2 2016-03-14 11:04:27.548 KVC[1102:348596] 我将要去-北京,通过valueForKeyPath:方法得知目的地的
对于私有成员属性,情况和上面类似,就不做过多赘述。
注:当通过key来访问类成员属性的时候,会查找这个类里与key相匹配的实例变量(_key, key, _isKey, isKey)。比如说下面这句代码:
1 [self.person setValue:@"小王" forKey:@"name"];
会匹配_name、name、_isName、isName这四个实例变量,当同时存在时,优先级为:_name>_isName>name>isName;当这四个实例变量都不存在时,通过valueForKey:或者valueForKeyPath: 这两个方法获取变量程序会报错。
这里对于KVC的简单介绍就完结了,如果需要了解setValue:forKey方法的实现原理,可以参考这篇文章:http://www.jianshu.com/p/d54af904967b
2.KVO
KVO是Key-Value Observing的缩写,翻译为键值观察,是一种监听机制,当指定的对象的属性被修改后,则观察者就会接受到通知。
KVO的使用步骤:给对象添加指定路径的观察者,设置观察者的监听回调,移除观察者。
(1)在上面的基础上,设置Person为观察者,观察其成员addr的address属性的变化。
1 -(instancetype)init{
2
3 self = [super init];
4 if(self){
5
6 _addr = [[Address alloc] init];
7 //给_addr添加观察者为当前类self,观察其属性address的变化
8 //若address属性发生变化,则通过回调方法observeValueForKeyPath:方法告知
9 //context是可以传给回调方法的内容
10 [_addr addObserver:self forKeyPath:@"address" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
11 }
12
13 return self;
14 }
(2)重写回调方法,输出addr的address属性改变前后的值。
1 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
2
3 NSLog(@"------ 分隔线 ------");
4 NSLog(@"原来的地址是:%@",change[NSKeyValueChangeOldKey]);
5 NSLog(@"现在的地址是:%@",change[NSKeyValueChangeNewKey]);
6 }
(3)移除观察者。
1 -(void)dealloc{
2
3 [_addr removeObserver:self forKeyPath:@"address"];
4 }
然后我们改变两次addr的address属性。
1 self.person.addr.address = @"老家";
2 [self.person setValue:@"深圳" forKeyPath:@"addr.address"];
运行结果如下:
1 2016-03-14 11:22:44.370 KVC[1199:448041] ------ 分隔线 ------
2 2016-03-14 11:22:44.371 KVC[1199:448041] 原来的地址是:<null>
3 2016-03-14 11:22:44.371 KVC[1199:448041] 现在的地址是:老家
4 2016-03-14 11:22:44.371 KVC[1199:448041] ------ 分隔线 ------
5 2016-03-14 11:22:44.371 KVC[1199:448041] 原来的地址是:老家
6 2016-03-14 11:22:44.371 KVC[1199:448041] 现在的地址是:深圳
这样KVO的简单流程就完成了。