浅谈-KVC-的实现原理

语言: CN / TW / HK

KVCKeyValue Coding 的简称,俗称“键值编码”,遵循 NSKeyValueCoding 协议,它是一种可以直接通过字符串的名字 key 来访问类属性的机制,而不是通过调用 settergetter 方法访问。

对于 KVCCocoa 自动放入和取出基本数据类型放入 NSNumberNSValue 中,当使用 setValue:ForKey: 或者 valueForKey: 时,它自动将基本数据类型从这些对象中取出,仅 KVC 具有这种自动包装功能,常规方法调用和属性语法不具备该功能。

setValue:forKey 的实现方式:

以字符串的形式向对象发送消息,如果成员用 @property ,因为 @synthsize 告诉编译器自动生成 set<Key>: 格式的 setter 方法,所以这种情况下会直接搜索到。

首先查找以 set<Key> 命名的 setter 方法,如果没有找到查找以 _set<Key> 命名的方法;如果上面的方法没有找到,如果类方法 accessInstanceVariablesDirectly 返回 YES ,那么将在对象内部查找名为 _<key>_is<Key><key>is<key> 的实例变量。如果找到则设置成员的值,如果没有查找调用 setValue:forUndefinedKey:

valueForKey: 的实现方式:

  • 首先查找以 get<Key><key>is<Key> 命名的 getter 方法,找到直接调用。
  • 如果上面的 getter 没有找到,则会查找 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 提供属性值确认的 API,它可以用来检查 set 的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。

实现核查方法,为如下格式: 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