KVC(Key-value coding)鍵值編碼,iOS的開發(fā)中,可以允許開發(fā)者通過Key名直接訪問對(duì)象的屬性浓瞪,或者給對(duì)象的屬性賦值。而不需要調(diào)用明確的存取方法二庵。這樣就可以在運(yùn)行時(shí)動(dòng)態(tài)在訪問和修改對(duì)象的屬性。而不是在編譯時(shí)確定缓呛,這也是iOS開發(fā)中的黑魔法之一催享。
KVC在iOS中的定義
無論是Swift還是Objective-C,KVC的定義都是對(duì)NSObject的擴(kuò)展來實(shí)現(xiàn)的(Objective-c中有個(gè)顯式的NSKeyValueCoding類別名哟绊,而Swift沒有因妙,也不需要)所以對(duì)于所有繼承了NSObject在類型,都能使用KVC(一些純Swift類和結(jié)構(gòu)體是不支持KVC的)票髓,下面是KVC最為重要的四個(gè)方法
- (nullableid)valueForKey:(NSString*)key;//直接通過Key來取值
- (void)setValue:(nullableid)value forKey:(NSString*)key;//通過Key來設(shè)值
- (nullableid)valueForKeyPath:(NSString*)keyPath;//通過KeyPath來取值
- (void)setValue:(nullableid)value forKeyPath:(NSString*)keyPath;//通過KeyPath來設(shè)值
當(dāng)然NSKeyValueCoding類別中還有其他的一些方法攀涵,下面列舉一些
上面的這些方法在碰到特殊情況或者有特殊需求還是會(huì)用到的,所以也是可以了解一下洽沟。后面的代碼示例會(huì)有講到其中的一些方法以故。
同時(shí)蘋果對(duì)一些容器類比如NSArray或者NSSet等,KVC有著特殊的實(shí)現(xiàn)裆操。建議有基礎(chǔ)的或者英文好的開發(fā)者直接去看蘋果的官方文檔怒详,相信你會(huì)對(duì)KVC的理解更上一個(gè)臺(tái)階炉媒。
KVC是怎么尋找Key的
KVC是怎么使用的,我相信絕大多數(shù)的開發(fā)者都很清楚昆烁,我在這里就不再寫簡(jiǎn)單的使用KVC來設(shè)值和取值的代碼了吊骤,首頁我們來探討KVC在內(nèi)部是按什么樣的順序來尋找key的。
當(dāng)調(diào)用setValue:屬性值 forKey:@”name“的代碼時(shí)善玫,底層的執(zhí)行機(jī)制如下:
程序優(yōu)先調(diào)用set:屬性值方法水援,代碼通過setter方法完成設(shè)置。注意茅郎,這里的是指成員變量名,首字母大清寫要符合KVC的全名規(guī)則或渤,下同
如果沒有找到setName:方法系冗,KVC機(jī)制會(huì)檢查+ (BOOL)accessInstanceVariablesDirectly方法有沒有返回YES,默認(rèn)該方法會(huì)返回YES薪鹦,如果你重寫了該方法讓其返回NO的話掌敬,那么在這一步KVC會(huì)執(zhí)行setValue:forUNdefinedKey:方法,不過一般開發(fā)者不會(huì)這么做池磁。所以KVC機(jī)制會(huì)搜索該類里面有沒有名為_的成員變量奔害,無論該變量是在類接口部分定義,還是在類實(shí)現(xiàn)部分定義地熄,也無論用了什么樣的訪問修飾符华临,只在存在以_命名的變量,KVC都可以對(duì)該成員變量賦值端考。
如果該類即沒有set:方法雅潭,也沒有_成員變量,KVC機(jī)制會(huì)搜索_is的成員變量却特,
和上面一樣扶供,如果該類即沒有set:方法,也沒有_和_is成員變量裂明,KVC機(jī)制再會(huì)繼續(xù)搜索和is的成員變量椿浓。再給它們賦值。
如果上面列出的方法或者成員變量都不存在闽晦,系統(tǒng)將會(huì)執(zhí)行該對(duì)象的setValue:forUNdefinedKey:方法扳碍,默認(rèn)是拋出異常。
如果開發(fā)者想讓這個(gè)類禁用KVC里尼荆,那么重寫+ (BOOL)accessInstanceVariablesDirectly方法讓其返回NO即可左腔,這樣的話如果KVC沒有找到set:屬性名時(shí),會(huì)直接用setValue:forUNdefinedKey:方法捅儒。
下面我們來讓代碼來測(cè)試一下上面的KVC機(jī)制
首先我們先重寫accessInstanceVariablesDirectly方法讓其返回NO液样,再運(yùn)行代碼(注意上面注釋的部分)振亮,XCode直接打印出
這說明了重寫+(BOOL)accessInstanceVariablesDirectly方法讓其返回NO后,KVC找不到SetName:方法后,不再去找name系列成員變量鞭莽,而是直接調(diào)用forUndefinedKey方法
所以開發(fā)者如果不想讓自己的類實(shí)現(xiàn)KVC坊秸,就可以這么做。
下面那兩個(gè)setter和gettr的注釋取消掉澎怒,再把
NSString*name= [dog valueForKey:@"toSetName"]; 換成 NSString*name= [dog valueForKey:@"name"];
下面再注釋到accessInstanceVariablesDirectly方法褒搔,就能測(cè)試其他的key查找順序了,為了節(jié)省篇幅喷面,剩下的的KVC對(duì)于key尋找機(jī)制就不在這里展示了星瘾,有興趣的讀者可以寫代碼去驗(yàn)證。
當(dāng)調(diào)用ValueforKey:@”name“的代碼時(shí)惧辈,KVC對(duì)key的搜索方式不同于setValue:屬性值 forKey:@”name“琳状,其搜索方式如下
首先按get,,is的順序方法查找getter方法,找到的話會(huì)直接調(diào)用盒齿。如果是BOOL或者int等值類型念逞, 會(huì)做NSNumber轉(zhuǎn)換
如果上面的getter沒有找到,KVC則會(huì)查找countOf,objectInAtIndex,AtIndex格式的方法边翁。如果countOf和另外兩個(gè)方法中的要個(gè)被找到翎承,那么就會(huì)返回一個(gè)可以響應(yīng)NSArray所的方法的代理集合(它是NSKeyValueArray,是NSArray的子類)符匾,調(diào)用這個(gè)代理集合的方法叨咖,或者說給這個(gè)代理集合發(fā)送NSArray的方法,就會(huì)以countOf,objectInAtIndex,AtIndex這幾個(gè)方法組合的形式調(diào)用待讳。還有一個(gè)可選的get:range:方法芒澜。所以你想重新定義KVC的一些功能,你可以添加這些方法创淡,需要注意的是你的方法名要符合KVC的標(biāo)準(zhǔn)命名方法痴晦,包括方法簽名。
如果上面的方法沒有找到琳彩,那么會(huì)查找countOf誊酌,enumeratorOf,memberOf格式的方法。如果這三個(gè)方法都找到露乏,那么就返回一個(gè)可以響應(yīng)NSSet所的方法的代理集合碧浊,以送給這個(gè)代理集合消息方法,就會(huì)以countOf瘟仿,enumeratorOf,memberOf組合的形式調(diào)用箱锐。
如果還沒有找到,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認(rèn)行為)劳较,那么和先前的設(shè)值一樣驹止,會(huì)按_,_is,,is的順序搜索成員變量名浩聋,這里不推薦這么做,因?yàn)檫@樣直接訪問實(shí)例變量破壞了封裝性臊恋,使代碼更脆弱衣洁。如果重寫了類方法+ (BOOL)accessInstanceVariablesDirectly返回NO的話,那么會(huì)直接調(diào)用valueForUndefinedKey:
還沒有找到的話抖仅,調(diào)用valueForUndefinedKey:
下面再上代碼測(cè)試
很明顯坊夫,上面的代碼充分說明了說明了KVC在調(diào)用ValueforKey:@”name“時(shí)搜索key的機(jī)制。不過還有些功能沒有全部列出撤卢,有興趣的讀者可以寫代碼去驗(yàn)證环凿。
在KVC中使用KeyPath
然而在開發(fā)過程中,一個(gè)類的成員變量有可能是其他的自定義類凸丸,你可以先用KVC獲取出來再該屬性拷邢,然后再次用KVC來獲取這個(gè)自定義類的屬性,但這樣是比較繁瑣的屎慢,對(duì)此,KVC提供了一個(gè)解決方案忽洛,那就是鍵路徑KeyPath腻惠。
- (nullableid)valueForKeyPath:(NSString*)keyPath;//通過KeyPath來取值
- (void)setValue:(nullableid)value forKeyPath:(NSString*)keyPath;//通過KeyPath來設(shè)值
//打印結(jié)果
country1:China? country2:China
country1:USA? country2:USA
上面的代碼簡(jiǎn)單在展示了KeyPath是怎么用的。如果你不小心錯(cuò)誤的使用了key而非KeyPath的話欲虚,KVC會(huì)直接查找address.country這個(gè)屬性集灌,很明顯,這個(gè)屬性并不存在复哆,所以會(huì)再調(diào)用UndefinedKey相關(guān)方法欣喧。而KVC對(duì)于KeyPath是搜索機(jī)制第一步就是分離key,用小數(shù)點(diǎn).來分割key梯找,然后再像普通key一樣按照先前介紹的順序搜索下去唆阿。
KVC如何處理異常
KVC中最常見的異常就是不小心使用了錯(cuò)誤的Key,或者在設(shè)值中不小心傳遞了nil的值锈锤,KVC中有專門的方法來處理這些異常驯鳖。
通常在用KVC操作Model時(shí),拋出異常的那兩個(gè)方法是需要重寫的久免。雖然一般很小出現(xiàn)傳遞了錯(cuò)誤的Key值這種情況浅辙,但是如果不小心出現(xiàn)了,直接拋出異常讓APP崩潰顯然是不合理的阎姥。
一般在這里直接讓這個(gè)Key打印出來即可记舆,或者有些特殊情況需要特殊處理。
通常情況下呼巴,KVC不允許你要在調(diào)用setValue:屬性值 forKey:@”name“(或者keyPath)時(shí)對(duì)非對(duì)象傳遞一個(gè)nil的值泽腮。很簡(jiǎn)單御蒲,因?yàn)橹殿愋褪遣荒転閚il的。如果你不小心傳了盛正,KVC會(huì)調(diào)用setNilValueForKey:方法删咱。這個(gè)方法默認(rèn)是拋出異常,所以一般而言最好還是重寫這個(gè)方法豪筝。
KVC處理非對(duì)象和自定義對(duì)象
不是每一個(gè)方法都返回對(duì)象痰滋,但是valueForKey:總是返回一個(gè)id對(duì)象,如果原本的變量類型是值類型或者結(jié)構(gòu)體续崖,返回值會(huì)封裝成NSNumber或者NSValue對(duì)象敲街。這兩個(gè)類會(huì)處理從數(shù)字,布爾值到指針和結(jié)構(gòu)體任何類型严望。然后開以者需要手動(dòng)轉(zhuǎn)換成原來的類型多艇。盡管valueForKey:會(huì)自動(dòng)將值類型封裝成對(duì)象,但是setValue:forKey:卻不行像吻。你必須手動(dòng)將值類型轉(zhuǎn)換成NSNumber或者NSValue類型峻黍,才能傳遞過去。
對(duì)于自定義對(duì)象拨匆,KVC也會(huì)正確以設(shè)值和取值姆涩。因?yàn)閭鬟f進(jìn)去和取出來的都是id類型,所以需要開發(fā)者自己擔(dān)保類型的正確性惭每,運(yùn)行時(shí)Objective-C在發(fā)送消息的會(huì)檢查類型骨饿,如果錯(cuò)誤會(huì)直接拋出異常。
KVC與容器類
對(duì)象的屬性可以是一對(duì)一的台腥,也可以是一對(duì)多的宏赘。一對(duì)多的屬性要么是有序的(數(shù)組),要么是無序的(數(shù)組)
不可變的有序容器屬性(NSArray)和無序容器屬性(NSSet)一般可以使用valueForKey:來獲取黎侈。比如有一個(gè)叫items的NSArray屬性察署,你可以用valurForKey:@"items"來獲取這個(gè)屬性。前面valueForKey:的key搜索模式中蜓竹,我們發(fā)現(xiàn)其實(shí)KVC使用了一種更靈活的方式來管理容器類箕母。蘋果的官方文檔也推薦我們實(shí)現(xiàn)這些這些特殊的訪問器。
而當(dāng)對(duì)象的屬性是可變的容器時(shí)俱济,對(duì)于有序的容器嘶是,可以用下面的方法:
- (NSMutableArray*)mutableArrayValueForKey:(NSString *)key;
該方法返回一個(gè)可變有序數(shù)組,如果調(diào)用該方法蛛碌,KVC的搜索順序如下
搜索insertObject:inAtIndex:,removeObjectFromAtIndex:或者insertAdIndexes,removeAtIndexes格式的方法
如果至少找到一個(gè)insert方法和一個(gè)remove方法聂喇,那么同樣返回一個(gè)可以響應(yīng)NSMutableArray所有方法代理集合(類名是NSKeyValueFastMutableArray2),那么給這個(gè)代理集合發(fā)送NSMutableArray的方法,以insertObject:inAtIndex:,removeObjectFromAtIndex:或者insertAdIndexes,removeAtIndexes組合的形式調(diào)用希太。還有兩個(gè)可選實(shí)現(xiàn)的接口:replaceOnjectAtIndex:withObject: , replaceAtIndexes:with: 克饶。
如果上步的方法沒有找到,則搜索set:格式的方法誊辉,如果找到矾湃,那么發(fā)送給代理集合的NSMutableArray最終都會(huì)調(diào)用set:方法。 也就是說堕澄,mutableArrayValueForKey:取出的代理集合修改后邀跃,用·set:· 重新賦值回去去。這樣做效率會(huì)低很多蛙紫。所以推薦實(shí)現(xiàn)上面的方法拍屑。
如果上一步的方法還還沒有找到,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認(rèn)行為)坑傅,會(huì)按_,,的順序搜索成員變量名僵驰,如果找到,那么發(fā)送的NSMutableArray消息方法直接交給這個(gè)成員變量處理唁毒。
如果還是找不到蒜茴,調(diào)用valueForUndefinedKey:
關(guān)于mutableArrayValueForKey:的適用場(chǎng)景,我在網(wǎng)上找了很多浆西,發(fā)現(xiàn)其一般是用在對(duì)NSMutableArray添加Observer上矮男。
如果對(duì)象屬性是個(gè)NSMutableAArray、NSMutableSet室谚、NSMutableDictionary等集合類型時(shí),你給它添加KVO時(shí)崔泵,你會(huì)發(fā)現(xiàn)當(dāng)你添加或者移除元素時(shí)并不能接收到變化秒赤。因?yàn)镵VO的本質(zhì)是系統(tǒng)監(jiān)測(cè)到某個(gè)屬性的內(nèi)存地址或常量改變時(shí),會(huì)添加上- (void)willChangeValueForKey:(NSString *)key
和- (void)didChangeValueForKey:(NSString *)key方法來發(fā)送通知憎瘸,所以一種解決方法是手動(dòng)調(diào)用者兩個(gè)方法入篮,但是并不推薦,你永遠(yuǎn)無法像系統(tǒng)一樣真正知道這個(gè)元素什么時(shí)候被改變幌甘。另一種便是利用使用mutableArrayValueForKey:了潮售。
從上面的代碼可以看出,當(dāng)只是普通地調(diào)用[_arr addObject:@"1"]時(shí)锅风,Observer并不會(huì)回調(diào)酥诽,只有[[self mutableArrayValueForKey:@"arr"] addObject:@"1"];這樣寫時(shí)才能正確地觸發(fā)KVO。打印出來的數(shù)據(jù)中皱埠,可以看出這次操作的詳情肮帐,kind可能是指操作方法(我還不是很確認(rèn)),old和new并不是成對(duì)出現(xiàn)的,當(dāng)加添新數(shù)據(jù)時(shí)是new训枢,刪除數(shù)據(jù)時(shí)是old
而對(duì)于無序的容器托修,可以用下面的方法:
- (NSMutableSet*)mutableSetValueForKey:(NSString *)key;
該方法返回一個(gè)可變的無序數(shù)組如果調(diào)用該方法,KVC的搜索順序如下
搜索addObjectObject:,removeObject:或者add,remove格式的方法
如果至少找到一個(gè)insert方法和一個(gè)remove方法恒界,那么同樣返回一個(gè)可以響應(yīng)NSMutableSet所有方法代理集合(類名是NSKeyValueFastMutableSet2)睦刃,那么給這個(gè)代理集合發(fā)送NSMutableSet的方法,以addObjectObject:,removeObject:或者add,remove組合的形式調(diào)用十酣。還有兩個(gè)可選實(shí)現(xiàn)的接口:intersect , set:涩拙。
如果reciever是ManagedObject,那么就不會(huì)繼續(xù)搜索婆誓。
如果上步的方法沒有找到吃环,則搜索set: 格式的方法,如果找到洋幻,那么發(fā)送給代理集合的NSMutableSet最終都會(huì)調(diào)用set:方法郁轻。 也就是說,mutableSetValueForKey取出的代理集合修改后文留,用set:重新賦值回去去好唯。這樣做效率會(huì)低很多。所以推薦實(shí)現(xiàn)上面的方法燥翅。
如果上一步的方法還還沒有找到骑篙,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認(rèn)行為),會(huì)按_,,的順序搜索成員變量名森书,如果找到靶端,那么發(fā)送的NSMutableSet消息方法直接交給這個(gè)成員變量處理。
如果還是找不到凛膏,調(diào)用valueForUndefinedKey:
可見杨名,除了檢查reciever是ManagedObject以外,其搜索順序和mutableArrayValueForKey基本一至猖毫,
同樣台谍,它們也有對(duì)應(yīng)的keyPath版本
- (NSMutableArray*)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableSet*)mutableSetValueForKeyPath:(NSString *)keyPath;
KVC和字典
當(dāng)對(duì)NSDictionary對(duì)象使用KVC時(shí),valueForKey:的表現(xiàn)行為和objectForKey:一樣吁断。所以使用valueForKeyPath:用來訪問多層嵌套的字典是比較方便的趁蕊。
KVC里面還有兩個(gè)關(guān)于NSDictionary的方法
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
dictionaryWithValuesForKeys:是指輸入一組key,返回這組key對(duì)應(yīng)的屬性仔役,再組成一個(gè)字典掷伙。
setValuesForKeysWithDictionary是用來修改Model中對(duì)應(yīng)key的屬性。下面直接用代碼會(huì)更直觀一點(diǎn)
KVC的內(nèi)部實(shí)現(xiàn)機(jī)制
前面我們對(duì)析了KVC是怎么搜索key的骂因。所以如果明白了key的搜索順序炎咖,是可以自己寫代碼實(shí)現(xiàn)KVC的。在考慮到集合和keyPath的情況下,KVC的實(shí)現(xiàn)會(huì)比較復(fù)雜乘盼,我們只寫代碼實(shí)現(xiàn)最普通的取值和設(shè)值即可升熊。
KVC的使用
KVC在iOS開發(fā)中是絕不可少的利器,這種基于運(yùn)行時(shí)的編程方式極大地提高了靈活性绸栅,簡(jiǎn)化了代碼级野,甚至實(shí)現(xiàn)很多難以想像的功能,KVC也是許多iOS開發(fā)黑魔法的基礎(chǔ)粹胯。下面我來列舉iOS開發(fā)中KVC的使用場(chǎng)景
動(dòng)態(tài)地取值和設(shè)值
利用KVC動(dòng)態(tài)的取值和設(shè)值是最基本的用途了蓖柔。相信每一個(gè)iOS開發(fā)者都能熟練掌握,
用KVC來訪問和修改私有變量
對(duì)于類里的私有屬性风纠,Objective-C是無法直接訪問的况鸣,但是KVC是可以的,請(qǐng)參考本文前面的Dog類的例子竹观。
Model和字典轉(zhuǎn)換
Model就是MVC和MVVM最前面的M镐捧,顯然Model的重要性不言而喻。只有在將網(wǎng)絡(luò)&數(shù)據(jù)庫獲取的數(shù)據(jù)正確轉(zhuǎn)化成Model后,才能更好地服務(wù)ViewController和View臭增。通常--Model是應(yīng)用邏輯層的對(duì)象懂酱,如 Account、Order 等等誊抛。這些對(duì)象是你開發(fā)的應(yīng)用程序中的一些核心對(duì)象列牺,負(fù)責(zé)應(yīng)用的邏輯計(jì)算和諸多與業(yè)務(wù)相關(guān)的方法和操作。首先Model將未處理的數(shù)據(jù)轉(zhuǎn)化成Model后拗窃,再傳給ViewController瞎领,再傳給ViewController再將處理好的Model數(shù)據(jù)顯示到View上去。相反View產(chǎn)生的數(shù)據(jù)可也可以轉(zhuǎn)化為Model随夸,通過ViewConroller傳到Model層處理后再保存&更新默刚。在iOS開發(fā)中,Model還可以分為胖Model和瘦Model逃魄。當(dāng)然,這些東西都不在本文的討論范圍之內(nèi)澜搅。本文討論的是如何增強(qiáng)Model的一些功能伍俘,這些功能并不是業(yè)務(wù)邏輯上的功能,而是讓Model可以自動(dòng)實(shí)現(xiàn)一些代碼層面的功能勉躺“可以降低我們的代碼量,大量減少重復(fù)的代碼饵溅。
-(NSString)descprition{? ??
return 你對(duì)自定義類的各個(gè)變量的描述,從而可以打印出來
}
修改一些控件的內(nèi)部屬性
這也是iOS開發(fā)中必不可少的小技巧妨退。眾所周知很多UI控件都由很多內(nèi)部UI控件組合而成的,但是Apple度沒有提供這訪問這些空間的API,這樣我們就無法正常地訪問和修改這些控件的樣式咬荷。而KVC在大多數(shù)情況可下可以解決這個(gè)問題冠句。最常用的就是個(gè)性化UITextField中的placeHolderText了。
下面演示如果修改placeHolder的文字樣式幸乒。這里的關(guān)鍵點(diǎn)是如果獲取你要修改的樣式的屬性名懦底,也就是key或者keyPath名。
參考:http://www.reibang.com/p/45cbd324ea65