KVC 的原理分析

定義

KVC 是 Key Value Coding 的簡稱,鍵值對編碼,遵循 NSKeyValueCoding 協(xié)議,可以像操作字典一樣操作一個(gè)對象,通過 key 來直接取值和賦值的機(jī)制,而不是通過調(diào)用 setter、getter 方法訪問。

相關(guān)API

由下圖 NSKeyValueCoding.h 頭文件中,我們可以看到一些相關(guān)的 API。

1.png

其中,以下四個(gè)是我們較為常用的 API

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key; 

API 區(qū)別

這里簡單說一下 forKey 以及 forKeyPath 的區(qū)別。

@interface Student : NSObject
@property (nonatomic, assign) int age;
@end

@interface Teacher : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) Student *student;
@end

如上述代碼所示,一個(gè) Teacher 類中,包含一個(gè) name 的屬性和一個(gè) student的對象,Student 對象中包含 age 的屬性,那么,如下代碼所示可以看出,對于 Teacher 類中的 name 屬性使用 KVC 方法,無論是使用 setValue: forKey: 或是 setValue: forKeyPath: 都是可以實(shí)現(xiàn)的。但是,如果是對 student 中的 age,則必須使用 setValue: forKeyPath:方法。
所以,我們不難看出,key 是只能訪問當(dāng)前對象的屬性,如果想要層層向下訪問的話,就需要使用 keyPath

    self.teacher = [[Teacher alloc] init];
    self.teacher.name = @"老明";
    
    // 這里如果想要對 student 中的屬性進(jìn)行賦值,那么必須先對其進(jìn)行實(shí)例化
    self.teacher.student = [[Student alloc] init];
    self.teacher.student.age = 10;
    [self.teacher setValue:@"老李" forKey:@"name"];
    NSLog(@"teacherName1 = %@",self.teacher.name);
    NSLog(@"studentAge1 = %d",self.teacher.student.age);
    
    [self.teacher setValue:@"老劉" forKeyPath:@"name"];
    [self.teacher setValue:@30 forKeyPath:@"student.age"];
    NSLog(@"teacherName2 = %@",self.teacher.name);
    NSLog(@"studentAge2 = %d",self.teacher.student.age);

    log打印出來結(jié)果如下:
    teacherName1 = 老李
    studentAge1 = 10
    teacherName2 = 老劉
    studentAge2 = 30

是否觸發(fā) KVO

對 teacher 的 name 屬性執(zhí)行監(jiān)聽,查看其回調(diào)方法observeValueForKeyPath:ofObject:change:context:是否執(zhí)行

    // 1. 添加 KVO 監(jiān)聽
    [self.teacher addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    // 2. 通過 KVC 修改 name 屬性值
    [self.teacher setValue:@"teacherNew" forKey:@"name"];
    // 3. 移除監(jiān)聽
    [self.teacher removeObserver:self forKeyPath:@"name"];

log 打印結(jié)果如下:
 object: <Teacher: 0x600001f1ddc0>
keyPath: name
change: {
    kind = 1;
    new = teacherNew;
    old = "\U8001\U5218";
}

從上述打印結(jié)果不難看出,KVC 修改屬性會(huì)觸發(fā) KVO。

setValue:forKey: 原理

  1. setValue:forKey: 方法在調(diào)用時(shí),首先會(huì)去調(diào)用 setKey: 方法,如果找不到方法,則會(huì)查找調(diào)用 _setKey: 的方法,如果找到方法,那么直接傳遞參數(shù)調(diào)用方法,如果兩個(gè)方法均找不到,那么調(diào)用 accessInstanceVariablesDirectly
    其中 accessInstanceVariablesDirectly(是否能直接訪問成員變量) 方法的默認(rèn)返回值是YES。
  2. accessInstanceVariablesDirectly 返回 NO,則拋出異常。
  3. accessInstanceVariablesDirectly 返回 YES,則按順序 _key、_isKey、key、isKey 依次往后的順序去查找成員變量,如果找到成員變量,則直接賦值,找不到則拋出異常。

具體方法調(diào)用步驟,可參照下圖所示流程:

setValue:forKey:

如果所示步驟,可通過依次代碼設(shè)置 setKey 以及 _setKey: 方法來進(jìn)行驗(yàn)證。

valueForKey: 原理

通過 setValue:forKey: 方法,不難得出 valueForKey: 的執(zhí)行順序。

  1. 按順序 getKey、key、 isKey、_key 依次往后的順序去調(diào)用取值,如果找到方法,則直接調(diào)用方法。
  2. accessInstanceVariablesDirectly 返回 NO,則拋出異常。
  3. accessInstanceVariablesDirectly 返回 YES,則按順序 _key、_isKey、key、isKey 依次往后的順序去查找成員變量,如果找到成員變量,則直接取值,找不到則拋出異常。
    valueForKey:
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • KVC(Key-value coding)鍵值編碼,iOS的開發(fā)中,可以允許開發(fā)者通過Key名直接訪問對象的屬性,...
    CALayer_Sai閱讀 2,723評論 0 4
  • KVC是Key Value Coding的簡稱。它是一種可以通過字符串的名字(key)來訪問類屬性的機(jī)制。而不是通...
    153037c65b0c閱讀 11,750評論 15 17
  • KVC(Key-value coding)鍵值編碼,單看這個(gè)名字可能不太好理解。其實(shí)翻譯一下就很簡單了,就是指iO...
    我的夢工廠閱讀 940評論 1 8
  • KVC(Key-valuecoding)鍵值編碼,單看這個(gè)名字可能不太好理解。其實(shí)翻譯一下就很簡單了,就是指iOS...
    榕樹頭閱讀 773評論 0 2
  • KVC是Key Value Coding的簡稱。它是一種可以通過字符串的名字(key)來訪問類屬性的機(jī)制。而不是通...
    _李恒閱讀 797評論 0 0

友情鏈接更多精彩內(nèi)容