谈谈 Objective-C KVC

Objective-C 中,KVC(Key-Value Coding) 是一种使用字符串作为键值来间接访问对象属性的机制。在 NSObject 类中有如下两个方法:

1
2
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;

有时候为了方便我们会使用 -valueForKey: 替代属性的 getter 方法,用 -setValue:forKey: 替代 setter 方法。下面根据 <Foundation/NSKeyValueCoding.h> 头文件中的描述,我们来看一下 setValue:forKey: 方法的内部实现以及使用注意事项。

(1) 当调用一个对象的 -setValue:forKey: 方法时,首先会去该对象(接收者)对应的类中查找与 key 相匹配的 setter 方法(-set<Key>),如果找到了相应的方法,就检查方法的参数的类型。如果 setter 方法的参数是一个对象指针类型,就会直接调用上面匹配到的 setter 方法,并传入 value 作为参数。如果 setter 方法参数的类型不是一个对象指针类型,如基本类型等,但 value 值为 nil,就会调用 -setNilValueForKey: 方法,该方法的默认实现是抛出一个 NSInvalidArgumentException 异常,我们一般需要重写它。

(2) 当对象(接收者)没有找到 key 对应的 setter 方法时,如果接收者的类属性 +accessInstanceVariablesDirectly 返回 YES (默认值),就查找这个接收者是否存在实例变量的名称与 _<key>, _is<Key>, <key>, is<Key> (按照这个顺序)相匹配(例如,keyname,那么对象中如果存在名为 _name, _isName, name, isName 中的其中一个实例变量,就算匹配上了),如果找到这样一个实例变量,就使用 value 对其进行赋值。

(3) 当上述两个条件都不满足时,就会调用 -setValue:forUndefinedKey: 方法,该方法的默认实现是抛出一个 NSUndefinedKeyException 异常,我们一般需要重写这个方法以处理该异常。

另外,valueForKey: 方法的内部实现也与之类似,详见 NSKeyValueCoding.h 中的说明,这里不再赘述。

综上,当我们使用 setValue:forKey: 方法时,如果对象中 key 对应的属性或实例变量不存在,或者对于非对象指针类型传入的 value 值为 nil 时,都会抛出异常,如果我们没有进行处理,程序就会崩溃,所以我们一般会通过 category 的方式重写 NSObject-setNilValueForKey:-setValue:forUndefinedKey: 以及 valueForUndefinedKey 方法,如下图所示:

测试例子:

输出结果:

参考