博客連接KVC實現原理html
KVC
全稱是Key Value Coding
,定義在NSKeyValueCoding.h
文件中。KVC
提供了一種間接訪問其屬性方法或成員變量的機制,能夠經過字符串來訪問對應的屬性方法或成員變量。關於KVC的實現主要依賴於其搜索規則。面試
在賦值過程當中,咱們會使用- (void)setValue:(id)value forKey:(NSString *)key
或者(void)setValue:(id)value forKeyPath:(NSString *)keyPath;
進行KVC的賦值操做。在取值過程當中,咱們會使用- (id)valueForKey:(NSString *)key;
或者- (id)valueForKeyPath:(NSString *)keyPath;
。數組
KVC在經過key
或者keyPath
進行操做的時候,能夠查找屬性方法、成員變量等,查找的時候能夠兼容多種命名。具體的查找規則在KVC官方文檔中能夠找到。bash
KVC的實現主要依賴於setter
和getter
方法,因此關於命名須要符合蘋果的規範。另外在搜索過程當中accessInstanceVariablesDirectly
這個只讀屬性也起着重要的做用。這個屬性表示是否容許讀取實例變量的值,若是爲YES則在KVC查找的過程當中,從內存中讀取屬性實例變量的值,默認爲YES。app
以setValue:forKey:
爲例,其內部實現主要有如下步驟:測試
以set<Key>:
、_set<Key>
的順序查找對應命名的setter
方法,若是找到的話,調用這個方法並將值傳進去(根據須要進行對象轉換);ui
若是沒有發現setter
方法,可是accessInstanceVariablesDirectly
類屬性返回YES,則按_<key>
、_is<Key>
、<key>
、is<Key>
的順序查找一個對應的實例變量。若是發現則將value賦值給實例變量;spa
若是沒有發現setter
方法或實例變量,則調用setValue:forUndefinedKey:
方法,默認拋出一個異常,可是一個NSObject的子類能夠提出合適的行爲。指針
接着咱們用代碼進行相關的測試:code
實驗1:驗證setter方法
// model1
@interface KVCTestModel1 : NSObject
@end
@implementation KVCTestModel1
- (void)setName:(NSString *)name {
NSLog(@"%s", __func__);
}
- (void)_setName:(NSString *)name {
NSLog(@"%s", __func__);
}
@end
// model2
@interface KVCTestModel2 : NSObject
@end
@implementation KVCTestModel2
- (void)_setName:(NSString *)name {
NSLog(@"%s", __func__);
}
@end
// model3
@interface KVCTestModel3 : NSObject
@end
@implementation KVCTestModel3
@end
// 調用
- (void)_testKVC {
KVCTestModel1 *model1 = [[KVCTestModel1 alloc] init];
[model1 setValue:@"Nero" forKey:@"name"];
KVCTestModel2 *model2 = [[KVCTestModel2 alloc] init];
[model2 setValue:@"Nero" forKey:@"name"];
KVCTestModel3 *model3 = [[KVCTestModel3 alloc] init];
[model3 setValue:@"Nero" forKey:@"name"];
}
複製代碼
執行結果以下:
實驗2:驗證明例變量
// model1
@interface KVCTestModel1 : NSObject {
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
// model2
@interface KVCTestModel2 : NSObject {
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
// model3
@interface KVCTestModel3 : NSObject {
NSString *name;
NSString *isName;
}
@end
// model4
@interface KVCTestModel4 : NSObject {
NSString *isName;
}
@end
// 調用
- (void)_testKVC {
self.kvcTestModel1 = [[KVCTestModel1 alloc] init];
[self.kvcTestModel1 setValue:@"Nero" forKey:@"name"];
self.kvcTestModel2 = [[KVCTestModel2 alloc] init];
[self.kvcTestModel2 setValue:@"Nero" forKey:@"name"];
self.kvcTestModel3 = [[KVCTestModel3 alloc] init];
[self.kvcTestModel3 setValue:@"Nero" forKey:@"name"];
self.kvcTestModel4 = [[KVCTestModel4 alloc] init];
[self.kvcTestModel4 setValue:@"Nero" forKey:@"name"];
}
複製代碼
執行結果以下:
另外若是設置accessInstanceVariablesDirectly
返回爲NO,即便有符合命名規範的實例變量名,KVC也沒法賦值成功;setValue:forUndefinedKey:
默認會拋出一個異常,你能夠用重寫這個方法用來攔截。
賦值原理流程圖以下:
以valueForKey:
爲例,其內部實現主要有如下幾步:
經過getter
方法搜索實例,以get<Key>
, <key>
, is<Key>
, _<key>
的順序搜索符合規則的方法,若是有,就調用對應的方法;
若是沒有發現簡單getter方法
,而且在類方法accessInstanceVariablesDirectly
是返回YES的的狀況下搜索一個名爲_<key>
、_is<Key>
、<key>
、is<Key>
的實例;
若是返回值是一個對象指針,則直接返回這個結果;若是返回值是一個基礎數據類型,可是這個基礎數據類型是被NSNumber
支持的,則存儲爲NSNumber
並返回;若是返回值是一個不支持NSNumber
的基礎數據類型,則經過NSValue
進行存儲並返回;
在上述狀況都失敗的狀況下調用valueForUndefinedKey:
方法,默認拋出異常,可是子類能夠重寫此方法。
因爲和前面的賦值原理實驗類似,這裏就不添加相關的驗證代碼了。另外valueForKey:
返回的結果還多是數組或者其餘集合類型,因此在上面第1步和第2步之間還有一些其餘的規則,這些規則用來判斷是不是數組或者其餘集合類型的規則,可是我以爲忽略這些規則跟總體流程理解衝突不大,因此就忽略掉了(具體的在官在KVC官方文檔中能夠找到。)。
面試題分析: KVC可否可以觸發KVO
答案是確定的。
測試代碼以下:
@interface KVCTestModel1 : NSObject {
@public
NSString *_name;
}
@end
@implementation KVCTestModel1
@end
// 測試代碼
KVCTestModel1 *model1 = [[KVCTestModel1 alloc] init];
[model1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
// 直接修改爲員變量
model1 -> _name = @"Nero1";
NSLog(@"%@", [model1 valueForKey:@"name"]);
// 手動觸發KVO
[model1 willChangeValueForKey:@"name"];
model1 -> _name = @"Nero2";
[model1 didChangeValueForKey:@"name"];
NSLog(@"%@", [model1 valueForKey:@"name"]);
// KVC賦值
[model1 setValue:@"Nero3" forKeyPath:@"name"];
NSLog(@"%@", [model1 valueForKey:@"name"]);
[model1 removeObserver:self forKeyPath:@"name"];
複製代碼
打印結果:
經過上面的代碼咱們能夠認爲,在以KVC的方式對變量進行賦值的時候,會判斷該對象是否使用了KVO,若是是,則會觸發KVO。