定義
KVC 是 Key Value Coding 的簡稱,鍵值對編碼,遵循 NSKeyValueCoding 協(xié)議,可以像操作字典一樣操作一個(gè)對象,通過 key 來直接取值和賦值的機(jī)制,而不是通過調(diào)用 setter、getter 方法訪問。
相關(guān)API
由下圖 NSKeyValueCoding.h 頭文件中,我們可以看到一些相關(guān)的 API。

其中,以下四個(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: 原理
- setValue:forKey: 方法在調(diào)用時(shí),首先會(huì)去調(diào)用
setKey:方法,如果找不到方法,則會(huì)查找調(diào)用_setKey:的方法,如果找到方法,那么直接傳遞參數(shù)調(diào)用方法,如果兩個(gè)方法均找不到,那么調(diào)用accessInstanceVariablesDirectly。
其中 accessInstanceVariablesDirectly(是否能直接訪問成員變量) 方法的默認(rèn)返回值是YES。 - 若
accessInstanceVariablesDirectly返回 NO,則拋出異常。 - 若
accessInstanceVariablesDirectly返回 YES,則按順序_key、_isKey、key、isKey依次往后的順序去查找成員變量,如果找到成員變量,則直接賦值,找不到則拋出異常。
具體方法調(diào)用步驟,可參照下圖所示流程:

如果所示步驟,可通過依次代碼設(shè)置
setKey 以及 _setKey: 方法來進(jìn)行驗(yàn)證。
valueForKey: 原理
通過 setValue:forKey: 方法,不難得出 valueForKey: 的執(zhí)行順序。
- 按順序
getKey、key、 isKey、_key依次往后的順序去調(diào)用取值,如果找到方法,則直接調(diào)用方法。 - 若
accessInstanceVariablesDirectly返回 NO,則拋出異常。 - 若
accessInstanceVariablesDirectly返回 YES,則按順序_key、_isKey、key、isKey依次往后的順序去查找成員變量,如果找到成員變量,則直接取值,找不到則拋出異常。
valueForKey:
