定義
KVC 是 Key Value Coding 的簡稱龙屉,鍵值對編碼,遵循 NSKeyValueCoding
協(xié)議满俗,可以像操作字典一樣操作一個對象转捕,通過 key 來直接取值和賦值的機制,而不是通過調(diào)用 setter唆垃、getter 方法訪問五芝。
相關(guān)API
由下圖 NSKeyValueCoding.h
頭文件中,我們可以看到一些相關(guān)的 API辕万。
其中枢步,以下四個是我們較為常用的 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
如上述代碼所示渐尿,一個 Teacher
類中醉途,包含一個 name
的屬性和一個 student
的對象,Student
對象中包含 age
的屬性砖茸,那么隘擎,如下代碼所示可以看出,對于 Teacher
類中的 name
屬性使用 KVC 方法凉夯,無論是使用 setValue: forKey:
或是 setValue: forKeyPath:
都是可以實現(xiàn)的货葬。但是采幌,如果是對 student
中的 age
,則必須使用 setValue: forKeyPath:
方法震桶。
所以休傍,我們不難看出,key
是只能訪問當(dāng)前對象的屬性尼夺,如果想要層層向下訪問的話尊残,就需要使用 keyPath
。
self.teacher = [[Teacher alloc] init];
self.teacher.name = @"老明";
// 這里如果想要對 student 中的屬性進行賦值淤堵,那么必須先對其進行實例化
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 修改屬性會觸發(fā) KVO拐邪。
setValue:forKey: 原理
- setValue:forKey: 方法在調(diào)用時慰毅,首先會去調(diào)用
setKey:
方法,如果找不到方法扎阶,則會查找調(diào)用_setKey:
的方法汹胃,如果找到方法,那么直接傳遞參數(shù)調(diào)用方法东臀,如果兩個方法均找不到着饥,那么調(diào)用accessInstanceVariablesDirectly
。
其中 accessInstanceVariablesDirectly(是否能直接訪問成員變量) 方法的默認(rèn)返回值是YES惰赋。 - 若
accessInstanceVariablesDirectly
返回 NO宰掉,則拋出異常。 - 若
accessInstanceVariablesDirectly
返回 YES赁濒,則按順序_key轨奄、_isKey、key拒炎、isKey
依次往后的順序去查找成員變量挪拟,如果找到成員變量,則直接賦值击你,找不到則拋出異常玉组。
具體方法調(diào)用步驟,可參照下圖所示流程:
如果所示步驟丁侄,可通過依次代碼設(shè)置
setKey
以及 _setKey:
方法來進行驗證球切。
valueForKey: 原理
通過 setValue:forKey:
方法,不難得出 valueForKey:
的執(zhí)行順序绒障。
- 按順序
getKey吨凑、key、 isKey、_key
依次往后的順序去調(diào)用取值鸵钝,如果找到方法糙臼,則直接調(diào)用方法。 - 若
accessInstanceVariablesDirectly
返回 NO恩商,則拋出異常变逃。 - 若
accessInstanceVariablesDirectly
返回 YES,則按順序_key怠堪、_isKey揽乱、key、isKey
依次往后的順序去查找成員變量粟矿,如果找到成員變量凰棉,則直接取值,找不到則拋出異常陌粹。