Key Value Coding->KVC鍵值編碼

1、介紹:

KVC鍵值編碼在iOS中允許開發者通過 Key 直接訪問對象的屬性,或者給對象的屬性或者成員變量賦值,而不需要調用setter和getter方法。這樣就可以在運行時動態在訪問和修改對象的屬性。
? 作為可以稱為基類的NSObject有一個分類NSKeyValueCoding, KVC的定義都是對NSObject的擴展來實現的,所以對于所有繼承了NSObject在類型,都能使用KVC。
下面就該類中屬性方法做分析,同時配合一些例子來展示KVC的功能與實際運用。

2、屬性方法分析:

(1.)

@property (class, readonly) BOOL accessInstanceVariablesDirectly;

該屬性用來判斷是否能直接訪問實例變量,而且是readonly屬性的,但在實際運用時實際上是重寫類方法 + (BOOL)accessInstanceVariablesDirectly; 該類方法默認返回YES,如果設置為NO,則只會對指定的key進行精準搜索(e.g: key 為 @"name",則只會對名字為@"name"的屬性進行搜索匹配,不會在搜索@"name"無結果的情況下再去對包含name字符的一些其他屬性或成員變量或者方法進行搜索),若沒有結果則直接進入異常方法:取值異常方法 --> - (nullable id)valueForUndefinedKey:(NSString *)key;或者 賦值異常方法 --> - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

(2.)

- (nullable id)valueForKey:(NSString *)key;

直接通過key來取值。蘋果源碼中對該方法的描述使用了大量的篇幅(這里就不附加英文描述了,太長了~??),這些描述主要解釋了當調用- (nullable id)valueForKey:(NSString *)key; 方法時KVC在內部是按什么樣的順序來尋找key的。
實現查找機制如下:

  • 一、首先按 get<Key>,<key>,is<Key> 的順序方法查找getter方法,如果找到這樣的方法的話會直接調用。同時如果返回值是對象類型則直接返回該對象,如果是BOOL或者int等基礎數據類型, 會做NSNumber轉換,否則其他的任意類型將被轉換成NSValue類型返回(現在不僅限于NSPoint, NRange, NSRect, and NSSize這四個結構體類型);

  • 二、如果通過上面的方法沒有找到的話,KVC會查找與-countOf<Key> ,-indexIn<Key>OfObject:,-objectIn<Key>AtIndex:,-<key>AtIndexes: 這些方法名匹配的方法。如果找到-countOf<Key> ,-indexIn<Key>OfObject:這兩個方法和剩余其他兩個方法中的一個(至少三個同時存在,同時前兩個還是必須的!如果這四個同時存在則會優先搜索-objectIn<Key>AtIndex:方法并返回依照這一方法檢索的結果,結果是一個包含三個元素的NSOrderedSet對象),那么就會返回一個可以響應NSOrderedSet(有序集合)所有方法的代理集合NSKeyValueOrderedSet,調用這個代理集合的方法,或者說給這個代理集合發送NSOrderedSet的方法,就會以countOf<Key>,-indexIn<Key>OfObject:,objectIn<Key>AtIndex,<Key>AtIndex這幾個方法組合的形式調用,消息被發送給原始接收者-valueForKey:。如果還實現了一個可選的方法-get<Ket>:range:方法, 為了最佳的性能該方法將在適當的時候被調用。

  • 三、如果返回有序集合的方法沒有找到,KVC會查找與-countOf<Key> ,-objectIn<Key>AtIndex:, -<key>AtIndexes:(對應-[NSArray objectsAtIndexes:]方法) 這些方法名匹配的方法。如果找到-countOf<Key> 這個方法和剩余其他兩個方法中的一個,那么就會返回一個可以響應NSArray所有方法的代理集合NSKeyValueArray,調用這個代理集合的方法,或者說給這個代理集合發送NSArray的方法,就會以countOf<Key>,objectIn<Key>AtIndex,<Key>AtIndexs這幾個方法組合的形式調用,消息被發送給原始接收者-valueForKey:。如果還實現了一個可選的方法-get<Key>:range:方法, 為了最佳的性能該方法將在適當的時候被調用。* 在這里一定要區別開和第二步順序的區別!!!*

  • 四、如果返回數組的方法沒有找到,KVC會查找與-countOf<Key>,-enumeratorOf<Key>,and -memberOf<Key>:(對應被NSSet類定義的方法) 這些方法名匹配的方法。如果這三個方法全部找到,那么就會返回一個可以響應NSSet所有方法的代理集合NSKeyValueSet,調用這個代理集合的方法,或者說給這個代理集合發送NSSet的方法,就會以-countOf<Key>,-enumeratorOf<Key>,and -memberOf<Key>:這幾個方法組合的形式調用,消息被發送給原始接收者-valueForKey:。* 在這里要區別開和第二步順序及第三步順序的區別!!!*

  • 五、如果以上getter方法和集合的訪問方法均未找到,那么會再檢查類方法+ (BOOL)accessInstanceVariablesDirectly;,如果返回YES(默認行為),會按_<key>,_is<Key>,<key>,is<Key>的順序搜索成員變量名,如果找到這樣的實例變量那么返回規則與第一步的返回值順序是一樣的。如果返回是NO,那么會直接調用- (nullable id)valueForUndefinedKey:(NSString *)key;方法。

  • 六、若通過以上方法均為找到,直接調用- (nullable id)valueForUndefinedKey:(NSString *)key;方法。

看了這么多的說明是不是有點懵??,下面我們用代碼來解釋這一切~:

步驟一示例:
Person * person = [[Person alloc] init];
NSString * personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
//下圖中是Person.m中代碼
示例代碼-1_1

可以看到搜索到-getName方法有結果后就不會再查找了。

示例代碼-1_2

但是下面有一種情況:

示例代碼-1_3

可以看到這里打印了null!不是說好的按順序查找的么?而且下面我也定義了-valueForUndefinedKey:方法(沒有在圖片中粘出來)同樣沒有走,這是為什么呢?因為我定義了名字為name的屬性!當你定義了屬性后,KVC不會去搜索-is<Key>方法!
?其實只要有確定的key屬性,則只會檢索兩個方法: -get<Key>,-<key>,如果這兩個方法沒有結果,則直接放回null。

示例代碼-1_4

以下情況全是在沒有key屬性的情況下進行的操作(再次強調以下:在沒有key屬性的情況下搜索步驟2,3,4,5,6才有可能發生!!!,否則只會搜索步驟1中的-get<Key>,-<key>這兩個方法)

步驟二示例:
Person * person = [[Person alloc] init];
id personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
NSLog(@"%@", NSStringFromClass([personName class]));
//下圖中是Person.m中代碼
示例代碼-2_1
示例代碼-2_2

由上面兩圖可以看到:返回一個包含三個元素的NSOrderedSet對象,且元素均相同,其中控制元素個數的是-countOf<Key>方法!!示例代碼-2_1中結果元素均為@77,示例代碼-2_2中結果只與-<key>AtIndexes:方法返回值的第一個元素有關系!

步驟三示例:
Person * person = [[Person alloc] init];
id personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
NSLog(@"%@", NSStringFromClass([personName class]));
示例代碼-3_1
示例代碼-3_2

這里同步驟二最大的區別就是沒有-indexIn<Key>OfObject:方法,若有這一方法絕不會搜索步驟3,4,5的步驟。

步驟四示例:
Person * person = [[Person alloc] init];
id personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
NSLog(@"%@", NSStringFromClass([personName class]));
示例代碼-4_1

這里必須這三個方法全部存在才可以!

步驟五示例:
Person * person = [[Person alloc] init];
[person setValueForAllMemberVariable];
id personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
NSLog(@"%@", NSStringFromClass([personName class]));
示例代碼-5_1
示例代碼-5_2

這里就不一一的貼圖展示效果了,有興趣的自己可以測試一下??。

至于步驟六,相信大多數人都遇到過~~

(3.)

- (void)setValue:(nullable id)value forKey:(NSString *)key;

直接通過key為對象屬性或者成員變量賦值。我們直接看KVC在內部是按什么樣的順序來尋找key的然后為對應屬性和成員變量賦值的。
實現機制如下:

  • 一、程序會優先調用-set<Key>:屬性值方法(setter方法),代碼通過setter方法完成設置。這時候如果value參數是 非對象類型 (這再次強調一下** 非對象類型!!**),則不允許將參數設為nil,否則就會調用- (void)setNilValueForKey:(NSString *)key方法,造成崩潰,為了防止出現這種情況可以重寫該方法,然后針對出現的nil做一些其他操作。如果value參數是基本數據類型(int,float 等)要先轉化為NANumber類型或者NAValue類型。

e.g:

 Person * person = [[Person alloc] init];
 [person setValue:nil forKey:@"name"];
 [person setValue:nil forKey:@"age"];
示例代碼-1_1
  • 二、如果沒有找到-set<Key>:方法(即不存在屬性key),KVC機制會檢查+ (BOOL)accessInstanceVariablesDirectly方法有沒有返回YES,默認該方法會返回YES,如果你重寫了該方法讓其返回NO的話,那么在這一步KVC會執行-setValue:forUndefinedKey:方法,當返回為YES時,所以KVC機制會按照 _<key>,_is<Key>, <key>, is<Key> 順序搜索該類里面有沒有這些名稱的成員變量,無論該變量是在類接口部分定義,還是在類實現部分定義,也無論用了什么樣的訪問修飾符,只在存在這樣的成員變量都可以對該成員變量賦值。

e.g:

示例代碼2-1
示例代碼2-2
  • 三、如果以上都沒有找到就會調用- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;這一方法。一般重寫防止異常。

(4、)

- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;

這一方法主要是在使用KVC賦值前驗證key與Value是否匹配的方法。同時這個方法系統不會自動去檢測,需要自己調用去檢測。默認返回YES。e.g:

- (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError
{
    if ([inKey isEqualToString:@"name"]) {
        if ([*ioValue isKindOfClass:[NSString class]]) {
            NSLog(@"yes == %d,ioValueClass == %@", YES, NSStringFromClass([*ioValue class]));
            return YES;
        }else{
            NSLog(@"no == %d,ioValueClass == %@", NO, NSStringFromClass([*ioValue class]));
            return NO;
        }
    }
    return YES;
}
示例代碼

(5、)

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;

使用該方法獲取一個可變集合(NSMutableArray),如果調用該方法則搜索順序如下:

  • 一、搜索-insertObject:in<Key>AtIndex:, -removeObjectFrom<Key>AtIndex:或者 -insert<Key>AdIndexes ,- remove<Key>AtIndexes 格式的方法。至少有一個insert方法和一個remove方法,那么才會返回一個可以響應NSMutableArray所有方法代理集合(類名是NSKeyValueFastMutableArray2),那么給這個代理集合發送NSMutableArray的方法,會以-insertObject:in<Key>AtIndex: ,-removeObjectFrom<Key>AtIndex:-insert<Key>AdIndexes , -remove<Key>AtIndexes組合的形式調用。除此之外還有兩個可選實現的接口:-replaceOnjectAtIndex:withObject: , -replace<Key>AtIndexes:with<Key>:

  • 二、如果沒有找到,則搜索-set<Key>:格式的方法,如果找到,那么發送給代理集合的NSMutableArray最終都會調用set<Key>:方法。 也就是說,-mutableArrayValueForKey:取出的代理集合修改后,會用用-set<Key>: 重新賦值回去。若沒有找到,則后面和調用- (void)setValue:(nullable id)value forKey:(NSString *)key;方法的搜索步驟相同。

要特別強調的是:當你單獨調用該方法時,是沒有任何效果的,必須與 NSMutableArray 的 添加/插入元素方法或者移除元素方法結合使用才會進行搜索~,所以使用該方法就會產生一個問題:這一方法調用既包含了取值搜索也包含了賦值搜索,所以在使用時如果key不是屬性或者相關成員變量,或者未開辟內存空間,則必然會拋出異常!

常用場景:KVO監聽可變集合類屬性時,如果直接操作可變集合(例如[self.xxxArray remove···])時,不會觸發KVO,但是通過[self mutableArrayValueForKey:key] remove···]則會觸發KVO。
e.g:

調用`mutableArrayValueForKey:`獲取一個可變數組,然后插入一條數據
Person類中的方法。第一次調用`-setNameArray:`方法是因為有一個開辟空間的操作`person.nameArray = [NSMutableArray array]; `
與上圖作對比,驗證步驟二

(6、)

- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key ;
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;

這兩個方法與 (5、) 中的運用方式基本相同,只是第一步檢索判斷的方法不同,所以在此不多贅述。有興趣的可以看一下官方文檔。

(7、)

- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath;
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;

這一部分方法的應用場景一般是,一個類的成員變量有可能是其他的自定義類,那么通過key的KVC方法一層一層的去獲取值或者賦值,就會比較繁瑣,keyPath就是解決這問題的。

e.g:

// Person.h
@interface Person : NSObject
@property (nonatomic,copy) NSString *name;
@end

// Country.h
@interface Country : NSObject
@property (nonatomic,strong) Person *person;
@end
結果驗證

使用keyPath要用點 . 去分割不同的key!其實質原理還是key方法的查找順序!

(8、)

// - valueForKey: 方法查找不到key時會調用,默認拋出NSUndefinedKeyException異常
- (nullable id)valueForUndefinedKey:(NSString *)key;

// -setValue:forKey: 方法查找不到key時會調用,默認拋出NSUndefinedKeyException異常
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

// -setValue:forKey:方法的key為基本類型時傳入的value為nil時調用。
- (void)setNilValueForKey:(NSString *)key;

這三個方法其實前面介紹其他方法時都已運用過了,大家可以自己實踐一下,印象會更深刻些??????。

(9、)

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

對于這兩個方法相信大家都不會陌生,前者是輸入一組key,查找key一一對應的屬性value,然后返回key和value組成的字典。主要用于模型轉字典!后者是輸入一個字典,根據字典的key一一對應類的屬性為其賦值。主要用于字典轉模型!在這里就不舉例說明了~~

2、KVC中的函數操作:

(1、)簡單集合運算符:
?共有:@avg平均值)、@sum)、@max最大值)、@min最小值@count對象總數)(只能用在集合對象中(NSArray,NSSet),對象屬性必須為基本數據類型,返回值均為NSNumber類型);

驗證簡單集合運算符功能

(2、)對象運算符
?共有@distinctUnionOfObjects(返回的元素都是唯一的,是去重以后的結果)、@unionOfObjects(返回的是所有的結果--全集)。它們的返回值都是NSArray。

驗證對象運算符功能

(3、)數組和集合運算符
? @distinctUnionOfArrays@unionOfArrays@distinctUnionOfSets,主要用于集合中包含集合的情況。

舉例
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容