KVC都不陌生,多多少少都用过,那么KVC内部原理是怎样的?KVC和KVO什么关系?使用KVC赋值会触发KVO吗?
先看两张图
setValue:forKey:的原理
kvc赋值
valueForKey:的原理
kvc取值
Person类
@interface Person : NSObject
@property (assign, nonatomic) int age;
@end
@implementation Person
@end
Observer类
@interface Observer : NSObject
@end
@implementation Observer
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"observeValueForKeyPath - %@", change);
}
@end
一. 使用KVC赋值
main函数
#import "Person.h"
#import "Observer.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Observer *observer = [[Observer alloc] init];
Person *person = [[Person alloc] init];
// 添加KVO监听
[person addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
// 通过KVC修改age属性
[person setValue:@1 forKey:@"age"];
// 移除KVO监听
[person removeObserver:observer forKeyPath:@"age"];
}
return 0;
}
运行上述程序,结果为:
observeValueForKeyPath - {
kind = 1;
new = 1;
old = 0;
}
很显然,使用KVC修改属性的值会触发KVO。那么内部实现原理是什么样的呢?接下来一起探究一下。
1. Person.m里边添加age的set方法
@implementation Person
- (void)setAge:(int)age
{
_age = age;
NSLog(@"setAge: - %d", age);
}
@end
再次运行程序,结果如下:
setAge: - 1
observeValueForKeyPath - {
kind = 1;
new = 1;
old = 0;
}
发现调用了age的set方法
2. 再次修改Person类,为了不让系统自动生成setAge,所以不再使用属性age了,而是使用成员变量_age。
@interface Person : NSObject{
@public
int _age;
}
@end
- (void)_setAge:(int)age
{
NSLog(@"_setAge: - %d", age);
}
结果如下:
_setAge: - 1
observeValueForKeyPath - {
kind = 1;
new = 0;
old = 0;
}
发现居然调用了_setAge。
3. Person.m添加方法
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
运行程序直接崩溃,报错如下:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Person 0x100564070> valueForUndefinedKey:]: this class is not key value coding-compliant for the key age.'
这个方法就是询问如果没有找到指定的方法,是否允许直接查找对应的成员变量,如果返回NO表示不允许查找,所以崩溃了。此方法默认会返回YES。
4. Person类修改如下:
@interface Person : NSObject{
@public
int isAge;
int _age;
int age;
int _isAge;
}
@end
@implementation Person
@end
运行程序,main函数里边在[person setValue:@1 forKey:@"age"];下面打断点,查看控制台如下
含四个成员变量
可以看到,_age时被赋值了
5. 如果此时删除掉_age这个变量会怎么样呢?
结果如下:
移除_age成员变量
从图中可以看出,此时调用
[person setValue:@1 forKey:@"age"];赋值给了_isAge这个成员边变量。
6. 如果继续删除掉_isAge,那么会赋值给age。
7. 如果继续删除掉age,那么会赋值给isAge。
8. Person实现如下方法:
- (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);
}
运行结果如下:
willChangeValueForKey - age
didChangeValueForKey - begin - age
observeValueForKeyPath - {
kind = 1;
new = 1;
old = 0;
}
didChangeValueForKey - end - age
调用了willChangeValueForKey和didChangeValueForKey,所以就会收到KVO的消息通知。
使用KVC赋值总结如下:
使用KVC给某一变量赋值,方法以及成员变量查找顺序为:
首先查找setKey方法,如果找不到该方法就调用_setKey方法,如果还没找到对应的方法就调用+ (BOOL)accessInstanceVariablesDirectly方法,看是否返回YES(默认返回YES),如果返回NO,直接抛出异常,如果返回YES,则查找成员变量,顺序依次是_key、_isKey、key、isKey。在上述方法查找或者变量查找过程中如果找到直接赋值,在赋值完成以后会调用willChangeValueForKey和didChangeValueForKey,此时就能收到观察者的消息通知。如果没找到调用- (void)setValue:(id)value forUndefinedKey:(NSString *)key,然后抛出异常。
直接查看最上方的图片逻辑更加清晰。
二. 使用KVC取值
1.Person含有以下四个方法
Person类如下:
@interface Person : NSObject
{
@public
int age;
int isAge;
int _isAge;
int _age;
}
@end
@implementation Person
- (int)getAge
{
return 11;
}
- (int)age
{
return 12;
}
- (int)isAge
{
return 13;
}
- (int)_age
{
return 14;
}
@end
main函数如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
NSLog(@"%@", [person valueForKey:@"age"]);
}
return 0;
}
运行结果为:
11
可见调用的是getAge方法。
2.Person移除getAge方法
再次运行结果为:
12
3.Person移除age方法
再次运行结果为:
13
4.Person移除isAge方法
再次运行结果为:
14
4.Person移除所有方法,Main修改如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person->_age = 10;
person->_isAge = 11;
person->age = 12;
person->isAge = 13;
NSLog(@"%@", [person valueForKey:@"age"]);
}
return 0;
}
运行结果为:
10
5.移除person->_age = 10;和int _age;
运行结果为:
11
6.移除person->_isAge = 11;和int _isAge;
运行结果为:
12
7.移除person->age = 12;和int age;
运行结果为:
13
KVC取值总结如下:
首先查找方法getKey,找到直接返回结果,没找到就找方法key,如果还是找不到就找方法isKey,如果还是找不到就找方法_key。如果还是找不到就查找成员变量顺序依次是_key、_isKey、key、isKey。找到直接获取值,找不到会到用- (id)valueForUndefinedKey:(NSString *)key,然后抛出异常。
直接查看最上方的图片逻辑更加清晰。










网友评论