博客鏈接KVC實現(xiàn)原理
KVC
全稱是Key Value Coding
,定義在NSKeyValueCoding.h
文件中洞斯。KVC
提供了一種間接訪問其屬性方法或成員變量的機制绑咱,可以通過字符串來訪問對應(yīng)的屬性方法或成員變量。關(guān)于KVC的實現(xiàn)主要依賴于其搜索規(guī)則娇澎。
搜索規(guī)則
在賦值過程中,我們會使用- (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
進行操作的時候伪很,可以查找屬性方法戚啥、成員變量等,查找的時候可以兼容多種命名锉试。具體的查找規(guī)則在KVC官方文檔中可以找到猫十。
KVC的實現(xiàn)主要依賴于setter
和getter
方法,所以關(guān)于命名需要符合蘋果的規(guī)范呆盖。另外在搜索過程中accessInstanceVariablesDirectly
這個只讀屬性也起著重要的作用拖云。這個屬性表示是否允許讀取實例變量的值,如果為YES則在KVC查找的過程中应又,從內(nèi)存中讀取屬性實例變量的值江兢,默認為YES。
賦值原理
以setValue:forKey:
為例丁频,其內(nèi)部實現(xiàn)主要有以下步驟:
以
set<Key>:
杉允、_set<Key>
的順序查找對應(yīng)命名的setter
方法邑贴,如果找到的話,調(diào)用這個方法并將值傳進去(根據(jù)需要進行對象轉(zhuǎn)換)叔磷;如果沒有發(fā)現(xiàn)
setter
方法拢驾,但是accessInstanceVariablesDirectly
類屬性返回YES,則按_<key>
改基、_is<Key>
繁疤、<key>
、is<Key>
的順序查找一個對應(yīng)的實例變量秕狰。如果發(fā)現(xiàn)則將value賦值給實例變量稠腊;如果沒有發(fā)現(xiàn)
setter
方法或?qū)嵗兞浚瑒t調(diào)用setValue:forUndefinedKey:
方法鸣哀,默認拋出一個異常架忌,但是一個NSObject的子類可以提出合適的行為。
接著我們用代碼進行相關(guān)的測試:
實驗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
// 調(diào)用
- (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"];
}
執(zhí)行結(jié)果如下:
實驗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
// 調(diào)用
- (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"];
}
執(zhí)行結(jié)果如下:
另外如果設(shè)置accessInstanceVariablesDirectly
返回為NO我衬,即使有符合命名規(guī)范的實例變量名叹放,KVC也無法賦值成功;setValue:forUndefinedKey:
默認會拋出一個異常挠羔,你可以用重寫這個方法用來攔截井仰。
賦值原理流程圖如下:
取值原理
以valueForKey:
為例,其內(nèi)部實現(xiàn)主要有以下幾步:
通過
getter
方法搜索實例破加,以get<Key>
,<key>
,is<Key>
,_<key>
的順序搜索符合規(guī)則的方法俱恶,如果有,就調(diào)用對應(yīng)的方法范舀;如果沒有發(fā)現(xiàn)簡單
getter方法
速那,并且在類方法accessInstanceVariablesDirectly
是返回YES的的情況下搜索一個名為_<key>
、_is<Key>
尿背、<key>
端仰、is<Key>
的實例;如果返回值是一個對象指針田藐,則直接返回這個結(jié)果荔烧;如果返回值是一個基礎(chǔ)數(shù)據(jù)類型,但是這個基礎(chǔ)數(shù)據(jù)類型是被
NSNumber
支持的汽久,則存儲為NSNumber
并返回鹤竭;如果返回值是一個不支持NSNumber
的基礎(chǔ)數(shù)據(jù)類型,則通過NSValue
進行存儲并返回景醇;在上述情況都失敗的情況下調(diào)用
valueForUndefinedKey:
方法臀稚,默認拋出異常,但是子類可以重寫此方法三痰。
由于和前面的賦值原理實驗相似吧寺,這里就不添加相關(guān)的驗證代碼了窜管。另外valueForKey:
返回的結(jié)果還可能是數(shù)組或者其他集合類型,所以在上面第1步和第2步之間還有一些其他的規(guī)則稚机,這些規(guī)則用來判斷是否是數(shù)組或者其他集合類型的規(guī)則幕帆,但是我覺得忽略這些規(guī)則跟整體流程理解沖突不大,所以就忽略掉了(具體的在官在KVC官方文檔中可以找到赖条。)失乾。
補充:
面試題分析: KVC能否能夠觸發(fā)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"]);
// 手動觸發(fā)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"];
打印結(jié)果:
通過上面的代碼我們可以認為纬乍,在以KVC的方式對變量進行賦值的時候碱茁,會判斷該對象是否使用了KVO,如果是仿贬,則會觸發(fā)KVO纽竣。