概述
KVC
简称 Key-ValueCoding
,是一个基于 NSKeyValueCoding
非正式协议的机制,可以允许开发者通过 Key
名直接访问对象的属性,或者给对象的属性赋值,而不需要调用明确的存取方法。
这样就可以在运行时动态访问和修改对象的属性,而不是在编译时确定,这也是iOS开发中的黑魔法之一,很多高级的 iOS 开发技巧都是基于 KVC
实现的。
KVC
使用的方法是在 NSObject
中定义的,所以继承 NSObject
的对象都支持 KVC
,基本上所有的 OC 对象都支持 KVC
。
基本使用
@interface JFPerson : NSObject {
int age;
}
@end
使用 KVC
设置和获取 person
对象的 age
属性值:
JFPerson *person = [[JFPerson alloc] init];
// 测试KVO
[person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[person setValue:@10 forKey:@"age"];
[person setValue:@20 forKeyPath:@"age"];
NSLog(@"%@", [person valueForKey:@"age"]);
NSLog(@"%@", [person valueForKeyPath:@"age"]);
经过测试发现 KVC
会触发 KVO
:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"%@", change);
}
这是因为 KVC
不仅仅只是给成员变量赋值,还调用了 willChangeValueForKey:
和 didChangeValueForKey:
方法。可以通过在 JFPerson
类中重写这两个方法去验证结果:
- (void)willChangeValueForKey:(NSString *)key
{
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey");
}
- (void)didChangeValueForKey:(NSString *)key
{
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey");
}
注意:xxxKeyPath
可以传嵌套对象的属性,KVC
获取值可以无视 readonly
。
setValue:forKey:查找流程
首先会去查找 setKey
或 _setKey
方法,找到了就传递参数调用方法。
如果没有找到,则检查 + (BOOL)accessInstanceVariablesDirectly
是否返回 YES
,默认值就是 YES
。如果返回 YES
则按顺序查找成员变量 _key
、_isKey
、key
、isKey
,找到直接赋值。
如果返回 NO
或者没有找到对应成员变量,则调用 setValue:forUndefinedKey:
方法并抛出异常 NSUnknownKeyException
。
valueForKey:查找流程
首先按顺序查找 getKey
、key
、isKey
、_key
查找方法,找到则调用方法。
如果没有找到,则检查 + (BOOL)accessInstanceVariablesDirectly
是否返回 YES
,默认值就是 YES
。如果返回 YES
则按顺序查找成员变量 _key
、_isKey
、key
、isKey
,找到直接取值。
如果返回 NO
或者没有找到对应成员变量,则调用 valueForUndefinedKey:
方法并抛出异常 NSUnknownKeyException
。
防止抛出异常
重写两个方法即可:
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
- (id)valueForUndefinedKey:(NSString *)key
{
return nil;
}