简介
KVC 是 KeyValue Coding 的简称,遵循 NSKeyValueCoding 协议,它是一种可以直接通过字符串的名字 key 来访问类属性的机制,而不是通过调用 setter、getter 方法访问。
对于 KVC,Cocoa 自动放入和取出基本数据类型放入 NSNumber 或 NSValue 中,当使用 setValue:ForKey: 或者 valueForKey: 时,它自动将基本数据类型从这些对象中取出,仅 KVC 具有这种自动包装功能,常规方法调用和属性语法不具备该功能。
- (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来设值
NSKeyValueCoding
+ (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转到字典。
例如,如果用 KVC 访问 BankAccount 类中并不存在的变量 age 时,直接Crash 了:

控制台打印出
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<BankAccount 0x102401a30> valueForUndefinedKey:]: this class is not key value coding-compliant for the key age.'
***
valueForUndefinedKey:方法抛出的异常,BankAccount 类重写 valueForUndefinedKey: 系统方法:

从控制台打印的结果上可以看出,当 KVC 找不到 age 这个值时,会查找 valueForUndefinedKey: 方法。
KVC 的查找流程
setValue:forKey 的查找方式:
以字符串的形式向对象发送消息,按照 set<Key> 、_set<Key> 命名规则查找,如果找到,传递参数,然后调用该方法;如果没有找到,则会查看类方法 accessInstanceVariablesDirectly 的返回值。如果返回 YES,那么将在对象内部查找名为 _<key>、_is<Key>、<key>、is<key> 的实例变量。如果找到则设置成员的值,如果没有查找调用 setValue:forUndefinedKey:,并抛出异常。
注意:
-
accessInstanceVariablesDirectly默认返回值为YES。 -
accessInstanceVariablesDirectly的返回值为NO,直接调用setValue:forUndefinedKey:,抛出异常。 - 如果成员用
@property,@synthsize处理,因为@synthsize告诉编译器自动生成set<Key>:格式的方法,所以这种情况下会直接搜索到。
valueForKey: 的查找方式:
- 查找以
get<Key>、<key>、is<Key>命名规则查找方法,找到直接调用。 - 如果没有找到,则会查找
countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes格式的方法,找到就会调用countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes方法,还有一个可选的get<Key>:range:方法。 - 若是还没查到,那么查找
countOf<Key>、enumeratorOf<Key>、memberOf<Key>:格式的方法,如果找到就调用countOf<Key>、enumeratorOf<Key>、memberOf<Key>:方法。 - 若是还没查到,那么查看类方法
accessInstanceVariablesDirectly的返回值。如果返回YES,那么将在对象内部查找名为_<key>、_is<Key>、<key>、is<key>的实例变量,如果找到则设置成员的值。 - 如果没有查找调用
valueForUndefinedKey:,并抛出异常。
综上,使用 KVC 访问属性的代价比直接使用存取方法性能开销要大。
值的正确性核查
在调用 KVC 时可以先进行验证,验证通过下面两个方法进行,支持 key 和 keyPath 两种方式。验证方法默认实现返回 YES,可以通过重写对应的方法修改验证逻辑,验证方法需要我们手动调用,并不会在进行 KVC 的过程中自动调用。
实现核查方法,为如下格式:validate<Key>:error:
- (BOOL)validateName:(id *)ioValue error:(NSError **)outError {
// The name must not be nil, and must be at least two characters long.
if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 2]) {
if (outError != NULL) {
NSString *errorString = NSLocalizedStringFromTable(
@"A Person's name must be at least two characters long", @"Person",
@"validation: too short name error");
NSDictionary *userInfoDict =
[NSDictionary dictionaryWithObject:errorString
forKey:NSLocalizedDescriptionKey];
*outError = [[[NSError alloc] initWithDomain:PERSON_ERROR_DOMAIN
code:PERSON_INVALID_NAME_CODE
userInfo:userInfoDict] autorelease];
}
return NO;
}
return YES;
}
调用核查方法:
validateValue:forKey:error:,默认实现会搜索 validate<Key>:error:格式的核查方法,找到则调用,未找到默认返回 YES。
在 validateValue 方法的内部实现中,如果传入的 value 或 key 有问题,可以通过返回 NO 来表示错误,并设置 NSError 对象。