KVC
的全稱是Key-Value Coding
间狂,即鍵值編碼
帽借,是一種由NSKeyValueCoding
非正式協(xié)議啟用的機(jī)制峻村,對(duì)象采用它來提供對(duì)其屬性的間接訪問
。當(dāng)一個(gè)對(duì)象符合鍵值編碼時(shí)忧勿,它的屬性可以通過一個(gè)簡(jiǎn)潔、統(tǒng)一的消息傳遞接口通過字符串參數(shù)來尋址瞻讽。這種間接訪問機(jī)制補(bǔ)充了實(shí)例變量及其關(guān)聯(lián)的訪問方法所提供的直接訪問鸳吸。
KVC相關(guān)API
常用方法
- 通過
key
設(shè)值/取值
//直接通過Key來取值
- (nullable id)valueForKey:(NSString *)key;
//通過Key來設(shè)值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- 通過
keyPath(路由)
設(shè)值/取值
//通過KeyPath來取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
//通過KeyPath來設(shè)值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
其他方法
//默認(rèn)返回YES,表示如果沒有找到Set<Key>方法的話速勇,會(huì)按照_key晌砾,_iskey,key烦磁,iskey的順序搜索成員养匈,設(shè)置成NO就不這樣搜索
+ (BOOL)accessInstanceVariablesDirectly;
//KVC提供屬性值正確性驗(yàn)證的API,它可以用來檢查set的值是否正確都伪、為不正確的值做一個(gè)替換值或者拒絕設(shè)置新值并返回錯(cuò)誤原因呕乎。
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//這是集合操作的API,里面還有一系列這樣的API陨晶,如果屬性是一個(gè)NSMutableArray猬仁,那么可以用這個(gè)方法來返回。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//如果Key不存在先誉,且KVC無法搜索到任何和Key有關(guān)的字段或者屬性湿刽,則會(huì)調(diào)用這個(gè)方法,默認(rèn)是拋出異常谆膳。
- (nullable id)valueForUndefinedKey:(NSString *)key;
//和上一個(gè)方法一樣叭爱,但這個(gè)方法是設(shè)值。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//如果你在SetValue方法時(shí)面給Value傳nil漱病,則會(huì)調(diào)用這個(gè)方法
- (void)setNilValueForKey:(NSString *)key;
//輸入一組key,返回該組key對(duì)應(yīng)的Value买雾,再轉(zhuǎn)成字典返回,用于將Model轉(zhuǎn)到字典杨帽。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
KVC設(shè)值的底層原理
在日常開發(fā)中漓穿,針對(duì)對(duì)象屬性的賦值,一般有以下兩種方式
- 直接通過
setter
方法賦值 - 通過
KVC鍵值編碼
的相關(guān)API賦值
HTPerson *p = [[HTPerson alloc] init];
p.name = @"name";
[p setValue:@"kvcName" forKey:@"name"];
??下面針對(duì)使用最多的KVC設(shè)值方法:setValue:forKey
注盈,來進(jìn)行其底層原理的探索晃危。
- 首先進(jìn)入
setValue:forKey
的聲明,發(fā)現(xiàn)是在Foundation
框架中,而Foundation
框架是不開源的
- 通過蘋果官方文檔Key-Value Coding Programming Guide僚饭,來研究
KVC
賦值流程
當(dāng)調(diào)用setValue:forKey:
設(shè)置屬性value
時(shí)震叮,其底層的執(zhí)行流程為
- 【第一步】首先查找是否有這三種
setter
方法,按照查找順序?yàn)?code>set<Key>:-->_set<Key>:
-->setIs<Key>:
- 如果
有其中任意一個(gè)setter方法
鳍鸵,則直接設(shè)置屬性的value
(注意:key是指成員變量名
苇瓣,首字符大小寫需要符合KVC的命名規(guī)范) - 如果都
沒有
,則進(jìn)入【第二步】
- 如果
- 【第二步】:如果沒有第一步中的三個(gè)簡(jiǎn)單的setter方法偿乖,則查找
accessInstanceVariablesDirectly
返回值击罪,返回NO
,則進(jìn)入【第三步】- 如果返回
YES
贪薪,則查找間接訪問的實(shí)例變量進(jìn)行賦值媳禁,查找順序?yàn)椋?code>_<key> -->_is<Key>
-><key>
->is<Key>
- 如果找到其中任意一個(gè)實(shí)例變量,則賦值
- 如果都沒有画切,則進(jìn)入【第三步】
- 如果返回
- 【第三步】如果
setter方法
或者實(shí)例變量
都沒有找到竣稽,系統(tǒng)會(huì)執(zhí)行該對(duì)象的setValue:forUndefinedKey:
方法,默認(rèn)拋出NSUndefinedKeyException
類型的異常
KVC取值底層原理
查看蘋果文檔Key-Value Coding Programming Guide槽唾,當(dāng)調(diào)用valueForKey:
時(shí)丧枪,其底層的執(zhí)行流程為
- 【第一步】首先查找
getter
方法光涂,按照get<Key>
--><key>
-->is<Key>
-->_<key>
的方法順序查找- 如果
找到
了庞萍,就調(diào)用它,并使用結(jié)果繼續(xù)【第五步】 - 如果
沒有找到
忘闻,則進(jìn)入【第二步】
- 如果
- 【第二步】如果第一步中的getter方法沒有找到钝计,KVC會(huì)查找
countOf<Key>
和objectIn<Key>AtIndex:
和<key>AtIndexes:
- 如果找到
countOf<Key>
和其他兩個(gè)中的一個(gè),則會(huì)創(chuàng)建一個(gè)響應(yīng)所有NSArray
方法的集合代理對(duì)象
齐佳,響應(yīng)所有NSArray方法返回該對(duì)象私恬,即NSKeyValueArray
,是NSArray
的子類
炼吴。代理對(duì)象隨后將接收到的所有NSArray消息轉(zhuǎn)換為countOf<Key>
本鸣,objectIn<Key>AtIndex:
和<key>AtIndexes:
消息的某種組合,用來創(chuàng)建鍵值編碼對(duì)象硅蹦。如果原始對(duì)象還實(shí)現(xiàn)了一個(gè)名為get<Key>:range:
之類的可選方法荣德,則代理對(duì)象也將在適當(dāng)時(shí)使用該方法(注意:方法名的命名規(guī)則要符合KVC的標(biāo)準(zhǔn)命名方法,包括方法簽名童芹。) - 如果沒有找到這三個(gè)訪問數(shù)組的涮瞻,請(qǐng)繼續(xù)進(jìn)入【第三步】
- 如果找到
- 【第三步】如果沒有找到上面的幾種方法,則會(huì)同時(shí)查找
countOf<Key>
假褪,enumeratorOf<Key>
和memberOf<Key>:
這三個(gè)方法- 如果這三個(gè)方法都找到,則會(huì)創(chuàng)建一個(gè)
響應(yīng)所有NSSet方法的集合代理對(duì)象
宁否,并返回該對(duì)象,此代理對(duì)象隨后將其收到的所有NSSet
消息轉(zhuǎn)換為countOf<Key>
,enumeratorOf<Key>
和memberOf<Key>:
消息的某種組合督怜,用于創(chuàng)建它的對(duì)象 - 如果還是沒有找到丰歌,則進(jìn)入【第四步】
- 如果這三個(gè)方法都找到,則會(huì)創(chuàng)建一個(gè)
- 【第四步】如果還沒有找到立帖,檢查類方法
InstanceVariablesDirectly
是否YES
,依次搜索_<key>
绰筛,_is<Key>
窿克,<key>
或is<Key>
的實(shí)例變量- 如果搜到敞恋,
直接獲取實(shí)例變量的值
,進(jìn)入【第五步】 - 如果類方法
InstanceVariablesDirectly
返回NO
,進(jìn)入【第六步】
- 如果搜到敞恋,
- 【第五步】根據(jù)搜索到的
屬性值的類型
,返回不同的結(jié)果- 如果是
對(duì)象指針
,則直接返回結(jié)果 - 如果是
NSNumber支持
的標(biāo)量類型矮锈,則將其存儲(chǔ)在NSNumber實(shí)例
中并返回它 - 如果是
NSNumber不支持
的標(biāo)量類型子眶,請(qǐng)轉(zhuǎn)換為NSValue對(duì)象
并返回該對(duì)象
- 如果是
- 【第六步】如果上面5步的方法
均失敗
,系統(tǒng)會(huì)執(zhí)行該對(duì)象的valueForUndefinedKey:
方法,默認(rèn)拋出NSUndefinedKeyException
類型的異常
使用路由訪問蝌麸,即keyPath
在日常開發(fā)中弟疆,一個(gè)類的成員變量有可能是自定義類或者其他的復(fù)雜數(shù)據(jù)類型同廉,一般的操作是蟆湖,我們可以先通過KVC獲取該屬性,然后再通過KVC獲取自定義類的屬性,就是比較麻煩晦鞋,還有另一種比較簡(jiǎn)便的方法,就是使用KeyPath
即路由
棺克,涉及以下兩個(gè)方法:setValue:forKeyPath:
和 valueForKeyPath:
//通過KeyPath來取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
//通過KeyPath來設(shè)值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
KVC使用場(chǎng)景
1悠垛、動(dòng)態(tài)設(shè)值和取值
- 常用的可以通過
setValue:forKey:
和valueForKey:
- 也可以通過
路由
的方式setValue:forKeyPath:
和valueForKeyPath:
2、通過KVC訪問和修改私有變量
在日常開發(fā)中娜谊,對(duì)于類的私有屬性
确买,在外部定義的對(duì)象,是無法直接訪問私有屬性的纱皆,但是對(duì)于KVC而言湾趾,一個(gè)對(duì)象沒有自己的隱私
,所以可以通過KVC修改和訪問任何私有屬性
3派草、多值操作(model和字典互轉(zhuǎn))
model和字典的轉(zhuǎn)換
可以通過下面兩個(gè)KVC的API實(shí)現(xiàn)
//字典轉(zhuǎn)模型
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
//模型轉(zhuǎn)字典
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
4搀缠、修改一些系統(tǒng)控件的內(nèi)部屬性
在日常開發(fā)中,我們知道近迁,很多UI控件都是在其內(nèi)部由多個(gè)UI控件組合而成艺普,這些內(nèi)部控件蘋果并沒有提供訪問的API,但是使用KVC可以解決這個(gè)問題鉴竭,常用的就是自定義tabbar
歧譬、個(gè)性化UITextField
中的placeHolderText
5、用KVC實(shí)現(xiàn)高階消息傳遞
在對(duì)容器類使用KVC時(shí)搏存,valueForKey:
將會(huì)被傳遞給容器中的每一個(gè)對(duì)象瑰步,而不是對(duì)容器本身進(jìn)行操作,結(jié)果會(huì)被添加到返回的容器中璧眠,這樣缩焦,可以很方便的操作集合
來返回 另一個(gè)集合
//KVC實(shí)現(xiàn)高階消息傳遞
- (void)transmitMsg{
NSArray *arrStr = @[@"english", @"franch", @"chinese"];
NSArray *arrCapStr = [arrStr valueForKey:@"capitalizedString"];
for (NSString *str in arrCapStr) {
NSLog(@"%@", str);
}
NSArray *arrCapStrLength = [arrCapStr valueForKeyPath:@"capitalizedString.length"];
for (NSNumber *length in arrCapStrLength) {
NSLog(@"%ld", (long)length.integerValue);
}
}