KVO-KVC的原理探究 - KVC篇
关于KVC的探究
基本介绍和使用
KVC全称Key-Value Coding 键值编码,可以通过Key来访问某个属性,常见的API:1
2
3
4
5- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
创建Person类、Animal类,添加属性如下:
1 | @interface Animal : NSObject |
如上,使用KVC赋值的方式为:1
2
3
4
5
6
7
8
9
10
11
12Animal * dog = [[Animal alloc] init];
Person * person = [[Person alloc] init];
person.dog = dog;
// Key
[person setValue:@11 forKey:@"age"];
// KeyPath
[person setValue:@123 forKeyPath:@"dog.height"];
NSLog(@"---- %@ -- %@", @(person.age), @(person.dog.height));
打印结果:
2018-07-19 23:00:48.546719+0800 KVC[53839:3059207] ---- 11 -- 123
可以看到直接赋值的话两中方式都可以,但是类似上面的dog.height嵌套的方式必须通过KeyPath的方式赋值。
KVC原理
进入Foundation里面查看 - (void)setValue:(nullable id)value forKey:(NSString *)key;
方法的注解可以了解到,KVC的赋值步骤
如下图:
1 | st=>start: 调用setValue:forKey |
以上为markdown语法,用的 MWebLite编写的,奈何blog不支持,所以直接将结果导图截图贴在下面:
进入Foundation里面查看 - (nullable id)valueForKey:(NSString *)key;
方法的注解可以了解到,KVC的取值步骤
如下图:
1 | st=>start: 调用valueForKey: |
接下来我们来验证一下:
setValue:forKey:赋值
使用上面的Person类,将属性全部删除,添加以下成员变量,重写两个set方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@interface Person : NSObject {
@public
int _age;
int _isAge;
int age;
int isAge;
}
@end
- (void)setAge:(NSInteger)age {
NSLog(@"--- setAge");
}
- (void)_setAge:(NSInteger)age {
NSLog(@"--- _setAge");
}
然后调用KVC给age赋值,可以看打印结果:1
2018-07-20 14:41:18.679275+0800 KVC[65810:3455316] setAge
此时调用的是 setAge:
方法,注释掉 setAge:
方法再次运行:1
2018-07-20 14:41:43.561291+0800 KVC[65834:3456053] _setAge
当这两个方法都没有实现的时候就会调用 accessInstanceVariablesDirectly
方法,若返回YES,则直接给成员变量赋值,如没有或返回值为NO则会调用 setValue:forUndefinedKey:
并且抛出NSUnKnownKeyException异常。
valueForKey:取值
重写流程图中的get方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22- (int)getAge {
return 11;
}
- (int)age {
return 12;
}
- (int)isAge {
return 13;
}
- (int)_age {
return 14;
}
- (int)_getAge {
return 15;
}
打印结果:
NSLog(@"---- %@", [person valueForKey:@"age"]);
如上我们可以查看当调用过的方法后就注释,直到执行完成所有的方法。我们可以控制 accessInstanceVariablesDirectly
方法的返回值来证明我们想要的结果。
思考一下KVC能触发KVO吗?
我们来测试一下,添加Observer类来监听并输出Log:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 Observer
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context {
NSLog(@"observeValueForKeyPath - %@", change);
}
测试代码:
Observer * ob = [Observer new];
Person * person = [[Person alloc] init];
[person addObserver:ob forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
[person setValue:@1 forKey:@"age"];
[person removeObserver:ob forKeyPath:@"age"];
打印结果为:
2018-07-20 15:19:30.526730+0800 KVC[67145:3507833] observeValueForKeyPath - {
kind = 1;
new = 1;
old = 0;
}
可以看到KVC确实调用了KVO,上一篇文章中我们了解到了KVO的实现,接下来可以大概验证一下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21在Person类中实现如下方法:
- (void)willChangeValueForKey:(NSString *)key {
NSLog(@"willChangeValueForKey");
[super willChangeValueForKey:key];
}
- (void)didChangeValueForKey:(NSString *)key {
NSLog(@"didChangeValueForKey-- begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey-- end");
}
打印结果:
2018-07-20 15:23:34.761496+0800 KVC[67256:3513685] willChangeValueForKey
2018-07-20 15:23:34.761836+0800 KVC[67256:3513685] didChangeValueForKey-- begin
2018-07-20 15:23:34.762151+0800 KVC[67256:3513685] observeValueForKeyPath - {
kind = 1;
new = 1;
old = 0;
}
2018-07-20 15:23:34.762202+0800 KVC[67256:3513685] didChangeValueForKey-- end
可以看到打印结果跟KVO是一样的,这里可以猜测苹果大大在KVC内部的实现:1
2
3[ ];
person->_age = 1;
[ ];
KVC相当于手动调用了KVO。