定义
全称 Key-Value-Coding ,又叫键值编码。
常用 API
- (nullable id)valueForKey:(NSString *)key; //直接通过Key来取值
- (void)setValue:(nullable id)value forKey:(NSString *)key; //通过Key来设值
- (nullable id)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过KeyPath来设值
+ (BOOL)accessInstanceVariablesDirectly;
//默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。
- (nullable id)valueForUndefinedKey:(NSString *)key;
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//和上一个方法一样,但这个方法是设值。
- (void)setNilValueForKey:(NSString *)key;
//如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
原理
赋值原理
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- 调用
setValue:forKey:, - 触发
setKey:,- 如果
setKey:不存在,则会寻找_setKey:, - 如果这两个方法都没有找到,会调用
accessInstanceVariablesDirectly,- 如果返回
NO, 意味着不允许访问成员变量,然后会调用setValue:forUndefineKey,并抛出异常。 - 如果返回
YES,会去根据_key、_isKey、key、isKey的顺序去修改,如果找到,则对成员变量进行赋值,如果没有找到,会调用setValue:forUndefineKey,并抛出异常
- 如果返回
- 如果

image.png
取值原理
- (nullable id)valueForKey:(NSString *)key;
调用
valueForKey:-
寻找
getKey:,如果找到则完成调用。- 否则寻找
key,如果找到则完成调用。- 否则寻找
isKey, 如果找到则完成调用。- 否则寻找
_key, 如果找到则完成调用。
- 否则寻找
- 否则寻找
- 否则寻找
-
如果上面四个方法都没有找到,则调用
accessInstanceVariablesDirectly:来判断是否允许寻找成员变量。-
如果返回
YES, 则寻找对应的成员变量。- 首先寻找
_key, 如果找到则返回。- 否则寻找
_isKey,如果找到则返回。- 否则寻找
key,如果找到则返回。- 否则寻找
isKey,如果找到则返回。- 如果都没找到,则调用
valueForUndefineKey:, 并抛出异常。
- 如果都没找到,则调用
- 否则寻找
- 否则寻找
- 否则寻找
- 首先寻找
如果返回
NO, 则直接调用valueForUndefineKey:, 并抛出异常。
-

image.png
空值异常
通常情况下, setValue: forKey: 在非对象属性(也就是基本类型)的时候,需要我们传递一个不是 nil 的值,当传递一个 nil 值时,会触发 setNilValueForKey : ,并引发崩溃,因此,我们需要重写这个方法。
异常:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<Person 0x100629230> setNilValueForKey]: could not set nil as the value for the key age.'
- (void)setNilValueForKey:(NSString *)key{
NSLog(@"key 的值为空了- %@",key);
}
思考
通过 KVC 修改属性会触发 KVO 吗?
会的。 不管有没有 set 方法都会触发。
可以看到即使我没有实现 set 方法,系统依然会触发 KVO 监听。

image.png
而当我们手动实现 willChangeValueForKey 和 didChangeValueForKey 可以发现,这两个方法是被调用了的。

image.png
可以看出,SDK 在找不到 setAge 和 _setAge 时,调用了 _NSSetValueAndNotifyForKeyInIvar,而这个函数为我们调用了 willChangeValueForKey 和 didChangeValueForKey 。

image.png
感谢大佬
https://www.jianshu.com/p/45cbd324ea65
https://www.jianshu.com/p/247e78fa36c2