KVC實現原理

博客連接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的實現主要依賴於settergetter方法,因此關於命名須要符合蘋果的規範。另外在搜索過程當中accessInstanceVariablesDirectly這個只讀屬性也起着重要的做用。這個屬性表示是否容許讀取實例變量的值,若是爲YES則在KVC查找的過程當中,從內存中讀取屬性實例變量的值,默認爲YES。app

賦值原理

setValue:forKey:爲例,其內部實現主要有如下步驟:測試

  1. set<Key>:_set<Key>的順序查找對應命名的setter方法,若是找到的話,調用這個方法並將值傳進去(根據須要進行對象轉換);ui

  2. 若是沒有發現setter方法,可是accessInstanceVariablesDirectly類屬性返回YES,則按_<key>_is<Key><key>is<Key>的順序查找一個對應的實例變量。若是發現則將value賦值給實例變量;spa

  3. 若是沒有發現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"];
}
複製代碼

執行結果以下:

kvc_setter1

實驗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"];
}
複製代碼

執行結果以下:

kvc_setter2

另外若是設置accessInstanceVariablesDirectly返回爲NO,即便有符合命名規範的實例變量名,KVC也沒法賦值成功;setValue:forUndefinedKey:默認會拋出一個異常,你能夠用重寫這個方法用來攔截。

賦值原理流程圖以下:

kvc_setter_process.jpg

取值原理

valueForKey:爲例,其內部實現主要有如下幾步:

  1. 經過getter方法搜索實例,以get<Key>, <key>, is<Key>, _<key>的順序搜索符合規則的方法,若是有,就調用對應的方法;

  2. 若是沒有發現簡單getter方法,而且在類方法accessInstanceVariablesDirectly是返回YES的的狀況下搜索一個名爲_<key>_is<Key><key>is<Key>的實例;

  3. 若是返回值是一個對象指針,則直接返回這個結果;若是返回值是一個基礎數據類型,可是這個基礎數據類型是被NSNumber支持的,則存儲爲NSNumber並返回;若是返回值是一個不支持NSNumber的基礎數據類型,則經過NSValue進行存儲並返回;

  4. 在上述狀況都失敗的狀況下調用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。

相關文章
相關標籤/搜索