简介
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
对象。