04 iOS底层原理 - KVC本质探究
老规矩,还是先来两个面试题:
一,通过KVC修改属性会触发KVO吗?
二,KVC的赋值和取值过程是怎样的?原理是什么?
什么是KVC呢?
KVC的全称是Key-Value Coding,即“键值编码”,可以通过一个key来访问某个属性
废话不多说,直接上代码
一,准备代码
1. 创建Student类 继承自NSObject
// 声明Student
@interface Student : NSObject
@property (nonatomic, assign) int height;
@end
// 声明Person
@interface Person : NSObject
{
@public
int _age;
int _isAge;
int age;
int isAge;
}
// 测试 setAge: 和 _setAge: 调用流程时,注调这句代码
@property (nonatomic, assign) int age;
@property (nonatomic, strong) Student *student;
@end
// 实现
@implementation Student
@end
@implementation Person
//- (int)getAge {
// return 18;
//}
//- (int)age {
// return 28;
//}
//- (int)isAge {
// return 38;
//}
//- (int)_age {
// return 48;
//}
- (void)setAge:(int)age {
_age = age;
NSLog(@"setAge == %d", age);
}
- (void)_setAge:(int)age {
NSLog(@"_setAge == %d", age);
}
/*
- (void)willChangeValueForKey:(NSString *)key {
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey");
}
- (void)didChangeValueForKey:(NSString *)key {
NSLog(@"didChangeValueForKey-begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey-end");
}
// 默认返回YES,表示如果没有找到setKey:/_setKey:方法的话,
// 会按照_key,_isKey,key,isKey的顺序搜索成员变量,设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}*/
@end
二,都有哪些KVC的方法呢?
赋值:
setter
setValue:forKey
setValue:forKeyPath
取值:
getter
valueForKey:
valueForKeyPath:
注意:
赋值和取值没有对应关系,
但是用setValue:forKeyPath赋值了.路径的值,
只能用getter和valueForKeyPath:方法获取
直接上代码
// KVC简单测试
- (void)kvcTest1 {
Person *person = [[Person alloc]init];
person.age = 10;
NSLog(@"%d", person.age);
NSLog(@"%@", [person valueForKey:@"age"]);
NSLog(@"%@", [person valueForKeyPath:@"age"]);
[person setValue:[NSNumber numberWithInt:11] forKey:@"age"];
[person setValue:@11 forKey:@"age"];
NSLog(@"%d", person.age);
NSLog(@"%@", [person valueForKey:@"age"]);
NSLog(@"%@", [person valueForKeyPath:@"age"]);
// forKeyPath 不仅可以修改当前类key的值,还可以修改其他类key的值
// 用setValue:forKeyPath赋值了.路径的值,
// 只能用getter和valueForKeyPath:方法获取
person.student = [[Student alloc]init];
[person setValue:@180 forKeyPath:@"student.height"];
NSLog(@"%d", person.student.height);
NSLog(@"%@", [person valueForKeyPath:@"student.height"]);
}
三,通过修改KVC的值可以触发KVO吗?
先看测试代码:
- (void)kvcTest2 {
Person *person = [[Person alloc]init];
// 添加KVO
[person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
// 通过KVC修改属性
[person setValue:@18 forKey:@"age"];
// 移除监听
[person removeObserver:self forKeyPath:@"age"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@-%@-%@", keyPath, object, change);
}
分两种情况:
1. setKey:和_setKey:方法都存在,或者至少存在一种
在Person类里面把这两个方法打开测试
- (void)setAge:(int)age {
_age = age;
NSLog(@"setAge == %d", age);
}
- (void)_setAge:(int)age {
NSLog(@"_setAge == %d", age);
}
打印结果:
age-<Person: 0x600003028220>-{
kind = 1;
new = 18;
old = 10;
}
通过打印结果,发现确实可以通过KVC修改属新触发KVO
2. 没有setKey:和_setKey:方法,只有成员变量,还会触发KVO吗?
在Person类里面把这两句注掉
//- (void)setAge:(int)age {
// _age = age;
// NSLog(@"setAge == %d", age);
//}
//
//- (void)_setAge:(int)age {
// NSLog(@"_setAge == %d", age);
//}
会的,首先会触发accessInstanceVariablesDirectly方法,返回yes,
然后按照_key,_isKey,key,isKey的顺序搜索成员变量,并赋值。
此时的KVC大概实现流程是这样的:
- 调用willChangeValueForKey
- 修改_key的值
- 调用didChangeValueForKey,didChangeValueForKey 中实现了KVO的监听事件
三,KVC赋值和取值原理
先看两张图:
1. setValue:forKey:赋值原理
image.png按照示意图,我这里解释下KVC取值的原理:
setValue:forKey:的内部实现,可以分以下几种情况:
1>. 有属性值key
不管实现不实现setKey:方法,默认都是实现了该方法的,因此_setKey:不管实现还是不实现,_setKey:方法都不会被调用,同样也都不会去访问_key,_isKey,key,isKey这些成员变量
2>. 没有属性值key
a> 如果 setKey:和_setKey: 方法都实现了,那么只会调用setKey:方法,但不会去访问_key,_isKey,key,isKey这些成员变量;
b> 如果 setKey:和_setKey: 只实现其中一个方法,那么就只调用实现的那个方法,但不会去访问_key,_isKey,key,isKey这些成员变量。
3>. 没有属性值key,setKey:和_setKey:方法也没实现
accessInstanceVariablesDirectly默认返回YES,表示如果没有找到setKey:/_setKey:方法的话,会按照_key,_isKey,key,isKey的顺序搜索成员变量。
4>. 其他情况
没有_key,_isKey,key,isKey成员变量,或者accessInstanceVariablesDirectly返回NO,那么,就直接抛出异常NSUnknownKeyException。
总结:通过1和2看出,其实和属性值key存不存在没关系,只要存在setKey:或_setKey:方法,就都不会去访问_key,_isKey,key,isKey这些成员变量
2. valueForKey:取值原理
image.png从上图可以看出,基本的流程和赋值是一样的,需要注意两点:
- 第一个红框框住的方法,是四个方法依次是:
getKey、key、isKey、_key - 第二个红框,如果没有找到相关的方法,
并且accessInstanceVariablesDirectly返回NO时,报的错误和赋值时的不一样。
调用:valueForUndefinedKey:并抛出错误NSUnknowKeyExcention
四,现在看看文章开头的面试题
一,通过KVC修改属性会触发KVO吗?
会触发KVO,因为KVC在本质上是调用了
willChangeValueForKey:和didChangeValueForKey:的
二,KVC的赋值和取值过程是怎样的?原理是什么?
这个过程就不说了,上面两幅图记住比啥都好
结束语:非常感谢,MJ老师,和Hank老师,收获挺多