KVC的原理探索

/ 0

概述

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_isKeykeyisKey,找到直接赋值。

如果返回 NO 或者没有找到对应成员变量,则调用 setValue:forUndefinedKey: 方法并抛出异常 NSUnknownKeyException

valueForKey:查找流程

首先按顺序查找 getKeykeyisKey_key 查找方法,找到则调用方法。

如果没有找到,则检查 + (BOOL)accessInstanceVariablesDirectly 是否返回 YES,默认值就是 YES。如果返回 YES 则按顺序查找成员变量 _key_isKeykeyisKey,找到直接取值。

如果返回 NO 或者没有找到对应成员变量,则调用 valueForUndefinedKey: 方法并抛出异常 NSUnknownKeyException

防止抛出异常

重写两个方法即可:

- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}

- (id)valueForUndefinedKey:(NSString *)key
{
    return nil;
}