一、KVC 很簡(jiǎn)單
KVC 很簡(jiǎn)單可都,每個(gè)人都會(huì)用,僅有的 API 如下:
1蚓耽、setValue: forKeyPath:
2汹粤、setValue: forKey:
3、valueForKeyPath:
4田晚、valueForKey:
前兩個(gè)是設(shè)置值(value),后兩個(gè)是通過(guò) key 或者 keyPath 獲取對(duì)應(yīng)的 value(值)国葬。
在接下來(lái)的介紹中贤徒,大家主要關(guān)注非常規(guī)用法。
二汇四、KVC 的實(shí)質(zhì)
通常所理解的 KVC 是:設(shè)置或者獲取某個(gè)屬性或者成員變量的值接奈。但是問(wèn)題來(lái)了,如果沒(méi)有對(duì)應(yīng)的屬性獲取成員變量通孽,又會(huì)怎樣呢序宦?接下來(lái)分批介紹一下。
2.1 常規(guī)用法
有一個(gè)屬性是這樣定義的:
// 名字
@property (nonatomic, copy) NSString* name;
我們可以直接通過(guò)屬性名使用 KVC:
KVCObject* kvcObj = [[KVCObject alloc] init];
// 通過(guò)屬性名設(shè)置具體的 value
[kvcObj setValue:@"CoderHG" forKey:@"name"];
// 通過(guò)屬性獲取具體的 value
NSString* name = [kvcObj valueForKey:@"name"];
NSLog(@"姓名: %@", name);
以上的設(shè)置與獲取 value 都會(huì)執(zhí)行其屬性的 setter 與 getter 方法背苦。
通過(guò)成員變量使用 KVC:
KVCObject* kvcObj = [[KVCObject alloc] init];
// 通過(guò)成員變量設(shè)置具體的 value
[kvcObj setValue:@"CoderHG" forKey:@"_name"];
// 通過(guò)成員變量獲取具體的 value
NSString* name = [kvcObj valueForKey:@"_name"];
NSLog(@"姓名: %@", name);
以上的設(shè)置與獲取 value 都 不 會(huì)執(zhí)行其屬性的 setter 與 getter 方法互捌。
是的、在開發(fā)中只需要知道這兩種常規(guī)的用法就足夠了行剂。但是對(duì)于程序員來(lái)說(shuō)除了開發(fā)秕噪、還有一個(gè)名詞叫 面試,如果面試的時(shí)候只知道上面的用法厚宰,那肯定是不夠的腌巾。
2.2 特別的 key 需要特別的處理
這里的 特別的意思是遂填,這個(gè) key 不是對(duì)應(yīng)的屬性,也并非對(duì)應(yīng)的成員變量澈蝙,也可以稱為 非常規(guī)吓坚。那么這種情況,KVC 又是如何處理的呢灯荧?看一下如下代碼:
KVCObject* kvcObj = [[KVCObject alloc] init];
// 非常規(guī)設(shè)置具體的 value
[kvcObj setValue:@"CoderHG" forKey:@"goddess"];
// 非常規(guī)獲取具體的 value
NSString* name = [kvcObj valueForKey:@"goddess"];
NSLog(@"姓名: %@", name);
其中 goddess 既不是屬性礁击、也不似成員變量。這種情況漏麦,如果不做任何的處理客税,直接運(yùn)行代碼,那肯定會(huì) crash 的撕贞。
setValue: forKey: 的 crash 日志:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason:
'[<KVCObject 0x60800001eaa0> setValue:forUndefinedKey:]:
this class is not key value coding-compliant for the key goddess.'
valueForKey: 的 crash 日志:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason:
'[<KVCObject 0x608000004c30> valueForUndefinedKey:]:
this class is not key value coding-compliant for the key goddess.'
這種情況在 KVC 中就直接沒(méi)救了么更耻?不是的,其實(shí)在 crash 之前做了很多的查找工作的捏膨。
setValue: forKey: 的查詢順序是這樣的:
- 1秧均、依次查找是否實(shí)現(xiàn)了這些對(duì)象方法:setKey:、_setKey:号涯。一旦找到其中的一個(gè)實(shí)現(xiàn)目胡,則直接調(diào)用,都沒(méi)有找到链快,則進(jìn)入下一步查找誉己。
- 2、第一步未找到域蜗,調(diào)用 +accessInstanceVariablesDirectly 方法巨双,如果返回為 NO,則直接 crash霉祸,返回為 YES筑累,則會(huì)進(jìn)入下一步。這個(gè)方法默認(rèn)返回 YES丝蹭。
- 3慢宗、第二步返回 YES, 則會(huì)繼續(xù)查找是否有如下的成員變量:_key、_isKey奔穿、key 與 isKey镜沽。找到則直接賦值,沒(méi)有找到則直接 crash贱田。
注意:第一步查找的是 方法淘邻,第三部查找的是 成員變量。
valueForKey: 的查詢順序是這樣的:
- 1湘换、按照順序依次查找這些對(duì)象方法:getKey宾舅、key统阿、isKey 與 _key。找到則執(zhí)行筹我,沒(méi)有找到則進(jìn)入下一步扶平。
- 2、調(diào)用 +accessInstanceVariablesDirectly 方法蔬蕊,如果返回為 NO结澄,則直接 crash,返回為 YES岸夯,則會(huì)進(jìn)入下一步麻献。這個(gè)方法默認(rèn)返回 YES。
3猜扮、第二步返回 YES, 則會(huì)繼續(xù)查找是否有如下的成員變量:_key勉吻、_isKey、key 與 isKey旅赢。找到則獲取對(duì)應(yīng)成員變量的值齿桃,沒(méi)有找到則直接 crash。
溫馨提示: 關(guān)于第2煮盼、3步短纵,兩種情況是類似的。
總結(jié)
其實(shí)寫本簡(jiǎn)書的重點(diǎn)是關(guān)注 非常規(guī)的情況僵控,主要是記住其查找步驟即可香到。大概就是先查找方法、在查找成員變量报破。
在寫本簡(jiǎn)書的時(shí)候养渴,我有一個(gè)試驗(yàn) Demo # OC2Nature,可以作為一個(gè)參考泛烙。具體請(qǐng)看 KVC 目錄。