浅谈KVC的本质及原理
KVC全称是Key-Value Coding,俗称"键值编码",可以通过一个key访问某个属性.
常见的API有:
-(void)setValue:(nullable id)value forKey:(NSString *)key;
-(void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (nullable id)valueForKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
❓通过KVC能否触发KVO?
我们通过这个问题开始研究KVC的本质?
我们创建一个HHPerson
类,添加一个age
属性,然后给这个age
属性添加一个KVO,使用KVC修改age
的值:
HHPerson *person = [[HHPerson alloc]init];
HHObserver *observer = [[HHObserver alloc]init];
[person addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[person setValue:@10 forKey:@"age"];
=================打印结果=================
2019-03-04 15:23:42.197654+0800 KVC_01[9905:1115065] age---{
kind = 1;
new = 10;
old = 0;
}
可以看见,KVO触发了,之前我们讲过KVO的本质,知道触发KVO的关键在于重写了setter
方法,既然KVC也能触发KVO,那就说明KVC也调用了setAge:
方法,我们重写setAge:
方法,打印一句话看看:
- (void)setAge:(int)age{
_age = age;
NSLog(@"setAge方法被调用");
}
=================打印结果=================
2019-03-04 15:28:24.318732+0800 KVC_01[9927:1116525] setAge方法被调用
2019-03-04 15:28:24.319088+0800 KVC_01[9927:1116525] age---{
kind = 1;
new = 10;
old = 0;
}
可以看到KVC访问属性的时候,的确调用了setAge:
方法.那么setVlaue:forKey:
是如何设值的呢?
我用一张图来演示setVlaue:forKey:
的执行顺序:
执行步骤:
1:setVlaue:forKey:
首先查找setKey:
方法,如果有就执行;如果没有再去查找_setKey:
方法.
2:如果没有找到_setKey:
方法,就去查看accessInstanceVariablesDirectly
方法的返回值,这个方法的意思是:是否允许直接进入对象的成员变量?
,如果返回YES,就按照_key
,_isKey
,key
,isKey
的顺序查找成员变量,如果找到直接赋值;如果没找到就抛出异常NSUnknownKeyException
.
3:如果accessInstanceVariablesDirectly
方法直接返回NO(默认返回YES),就直接抛出异常NSUnknownKeyException
.
我们先证明第一点,把HHPerson
类中属性删掉,然后重写setAge:
,_setAge:
方法:
- (void)setAge:(int)age{
NSLog(@"setAge方法被调用");
}
- (void)_setAge:(int)age{
NSLog(@"_setAge被调用");
}
然后使用KVC给age
赋值,注意:此时HHPerson
中已经没有属性age
:
HHPerson *person = [[HHPerson alloc]init];
[person setValue:@10 forKey:@"age"];
========================打印结果====================
2019-03-04 16:09:39.886100+0800 KVC_01[10105:1130147] setAge方法被调用
我们把setAge:
方法注释掉,再运行:
2019-03-04 16:10:18.558353+0800 KVC_01[10115:1130494] _setAge被调用
通过运行结果可以很清楚的看到,[person setValue:@10 forKey:@"age"];
会先去查找setAge:
方法,如果有就调用,如果没有再去查找_setAge:
方法.
接下来验证第二点,我们在HHPerson
注释掉刚才的setAge:
方法,然后重写accessInstanceVariablesDirectly
方法,然后返回NO,运行下看看效果:
再修改为return YES;
然后添加如下成员变量:
@interface HHPerson : NSObject
{
@public
int _age;
int _isAge;
int age;
int isAge;
}
@end
在运行一下看看结果:
有人可能会以为是成员变量顺序导致的,我们把成员变量的顺序打乱在执行看看:
@interface HHPerson : NSObject
{
@public
int age;
int _isAge;
int isAge;
int _age;
}
@end
我们把4个属性全注释掉,运行一下看看效果:
又报
NSUnknownKeyException
的错误.
我们之前讲过:如果修改一个类的成员变量,会不会触发KVO?
答案是:不会!
但是如果用KVC修改一个类的成员变量就会触发KVO.我们来验证一下:
// HHPerson 中只有一个成员变量 age
@interface HHPerson : NSObject
{
@public
int age;
// int _isAge;
// int isAge;
// int _age;
}
@end
// 使用 KVC 修改这个 age 的值
HHPerson *person = [[HHPerson alloc]init];
HHObserver *observer = [[HHObserver alloc]init];
[person addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[person setValue:@10 forKey:@"age"];
==================打印结果====================
2019-03-04 17:04:15.930503+0800 KVC_01[10255:1148495] age---{
kind = 1;
new = 10;
old = 0;
}
可以看到的确触发 KVO 了.这是为什么呢?
因为KVC赋值同样会调用willChangeValueForKey:
,didChangeValueForKey:
两个方法,我们在HHPerson
中重写这两个方法:
- (void)willChangeValueForKey:(NSString *)key{
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey:%@",key);
}
- (void)didChangeValueForKey:(NSString *)key{
NSLog(@"didChangeValueForKey:%@ -----begin",key);
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey:%@ -----end",key);
}
==================打印结果====================
2019-03-04 17:08:31.074916+0800 KVC_01[10292:1150309] willChangeValueForKey:age
2019-03-04 17:08:31.075122+0800 KVC_01[10292:1150309] didChangeValueForKey:age -----begin
2019-03-04 17:08:31.075304+0800 KVC_01[10292:1150309] age---{
kind = 1;
new = 10;
old = 0;
}
可以看到,这两个方法的确都被调用了,所以KVC给一个类的属性赋值也能触发KVO.
以上讲的都是KVC的设值方法,接下来我们讲解一下KVC的取值方法.其实跟设值方法过程差不多,我们也用一张图表示:
KVC取值流程
大家可以参照上面的赋值流程验证一下,我就不重复验证了,都差不多.
❓KVC的赋值过程和取值过程是怎样的?原理是什么?
答案:上面两张流程图