淺談-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