iOS 的KVC技术比较常用,可在运行时动态地对一个对象的属性赋值,并且如果该key
是有添加KVO监听
, 也会触发这个监听。下面是KVC的实现原理总结。
一、KVC的设值原理
根据上图可以很清晰的知道KVC的设值过程2+4
(即2个方法4个成员变量)。假设要设值的属性名为key
1> 当调用setValue:forKey:
或者setValue:forKeypath:
方法时,是首先查找对象方法 setKey:
,如果有则直接调用,就此完成了KVC; 如果没有这个方法则查找_ setKey:
方法调用,完成KVC。
2> 当上述2个方法都未找到,则调用对象的类方法 accessInstanceVariablesDirectly
的返回值,如果为NO, 表示不允许访问成员变量,则抛出异常如上图; 如果为YES, 则查找是否存在可以设置的成员变量,按照_key
、_isKey
、key
、isKey
顺序查找,找到了一个就不用继续往后查找了,直接对该成员变量赋值完成KVC,如果没有找到,则抛出如图异常。
二、KVC的取值原理
KVC的取值过程如图
4+4
(即4个方法+4个成员变量),假设要取值的属性为key
。1> 当调用
valueForkey:
方法时,首先按照顺序getKey
、key
、isKey
、_key
查找对象方法,如果找到了方法,则调用方法拿到返回值即完成KVC取值.
2> 如果上述方法都没有找到,则调用对象的类方法 accessInstanceVariablesDirectly
的返回值,如果为NO, 表示不允许访问成员变量,则抛出异常如上图; 如果为YES, 则查找是否存在可以取值的成员变量,按照_key
、_isKey
、key
、isKey
顺序查找,找到了一个就不用继续往后查找了,直接取该成员变量完成KVC取值,如果没有找到,则抛出如图异常。
三、KVC触发KVO
1. 如果有setKey:或者_setKey:方法
按照KVO原理有setKey:是会触发KVO通知的;下面是测试没有setKey:方法但有有_setKey: (在没有对应属性只有方法的情况下测试的)
@implementation Person{
NSString *_height;
}
+ (BOOL)accessInstanceVariablesDirectly{
return NO;
}
- (void)_setKey:(NSString *)key{
NSLog(@"%s", __func__);
}
- (NSString *)_key{
NSLog(@"%s", __func__);
return @"234";
}
@end
// 在控制器中测试代码
- (void)testKVCForKVO{
// 1. 无setKey,有_setKey
Person *person = [[Person alloc] init];
[person addObserver:self forKeyPath:@"key" options:NSKeyValueObservingOptionNew context:nil];
[person setValue:@"111" forKey:@"key"];
[person removeObserver:self forKeyPath:@"key"];// person在dealloc时未移除观察者会奔溃
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"observeValueForKeyPath: %@", change);
}
--- 结果是有setKey:或者_setKey方法就会触发KVO,原因是内部调用了KVO通知的函数_NSSetObjectValueAndNotify
2.如果没有KVC设置值的2个setter方法,单独对成员变量使用KVC会触发KVO吗?
如上代码中,对_height成员使用KVC设置值, Person类中方法+ (BOOL)accessInstanceVariablesDirectly返回值设置为YES;
测试代码如下
- (void)testKVCForKVO{
// 1. 无setKey,有_setKey
Person *person = [[Person alloc] init];
[person addObserver:self forKeyPath:@"_height" options:NSKeyValueObservingOptionNew context:nil];
[person setValue:@(180) forKey:@"_height"];
[person removeObserver:self forKeyPath:@"_height"];// person在dealloc时未移除观察者会奔溃
}
---结果也是会触发,原因则是内部会调用didChangeValueForKey:
手动触发KVO时的两个方法中的后一个
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
以上两种情况都会触发KVO。
以上完结