什么是KVC?
KVC的全稱叫Key-Value Coding,也叫做鍵值編碼,在apple官方文檔中是這么解釋的。
鍵值編碼是NSKeyValueCoding非正式協(xié)議支持的一種機(jī)制,對(duì)象采用這種機(jī)制來(lái)提供對(duì)其屬性的間接訪問(wèn)岂却。當(dāng)對(duì)象符合鍵值編碼時(shí),可通過(guò)簡(jiǎn)潔,統(tǒng)一的消息傳遞接口通過(guò)字符串參數(shù)訪問(wèn)其屬性躏哩,這種間接訪問(wèn)機(jī)制補(bǔ)充了實(shí)例變量及其關(guān)聯(lián)的訪問(wèn)器方法提供的直接訪問(wèn)署浩。
通常,您使用訪問(wèn)器方法來(lái)訪問(wèn)對(duì)象的屬性扫尺。一個(gè)get訪問(wèn)器(或getter)從一個(gè)屬性返回值筋栋,一個(gè)set訪問(wèn)器(或setter)給一個(gè)屬性設(shè)置值。在Objective-C中正驻,您還可以直接訪問(wèn)屬性的基礎(chǔ)實(shí)例變量弊攘。以任何一種方式訪問(wèn)??對(duì)象屬性都很簡(jiǎn)單,但是需要調(diào)用特定于屬性的方法或變量名拨拓。隨著屬性列表的增加或更改肴颊,訪問(wèn)這些屬性的代碼也必須如此。相反渣磷,與鍵值編碼兼容的對(duì)象提供了一個(gè)簡(jiǎn)單的消息傳遞接口,該接口在其所有屬性之間都是一致的授瘦。
鍵值編碼是許多其他Cocoa技術(shù)的基礎(chǔ)概念醋界,例如鍵值觀察,Cocoa綁定提完,Core Data和AppleScript-ability形纺。在某些情況下,鍵值編碼還可以幫助簡(jiǎn)化代碼徒欣。
1逐样、訪問(wèn)對(duì)象的屬性
- 屬性:這些是簡(jiǎn)單的值,例如標(biāo)量打肝,字符串或布爾值脂新。值對(duì)象(例如NSNumber)和其他不可變類型(例如NSColor)也被視為屬性。
- 一對(duì)一的關(guān)系:這些是具有自己屬性的可變對(duì)象粗梭。對(duì)象的屬性可以更改争便,而無(wú)需更改對(duì)象本身。例如断医,銀行帳戶對(duì)象可能具有所有者屬性滞乙,該屬性是Person對(duì)象的實(shí)例,而Person對(duì)象本身具有address屬性鉴嗤。所有者的地址可以更改斩启,而無(wú)需更改銀行帳戶持有的所有者屬性。
- 一對(duì)多關(guān)系:這些是集合對(duì)象醉锅。盡管也可以使用自定義集合類兔簇,但是通常使用NSArray或NSSet的實(shí)例來(lái)保存此類集合。
@interface BankAccount : NSObject
@property (nonatomic) NSNumber* currentBalance; // 一個(gè)屬性
@property (nonatomic) Person* owner; // 一對(duì)一的關(guān)系
@property (nonatomic) NSArray< Transaction* >* transactions; // 一對(duì)多的關(guān)系
@end
為了維護(hù)封裝,對(duì)象通常為其接口上的屬性提供訪問(wèn)器方法男韧。 對(duì)象的作者可以顯式地編寫(xiě)這些方法朴摊,也可以依靠編譯器自動(dòng)合成它們。 無(wú)論哪種方式此虑,使用這些訪問(wèn)器之一的代碼作者都必須在編譯屬性名稱之前將其寫(xiě)入代碼甚纲。 訪問(wèn)器方法的名稱成為使用它的代碼的靜態(tài)部分。 例如朦前,上述代碼中銀行行帳戶對(duì)象介杆,編譯器將合成一個(gè)可以為myAccount實(shí)例調(diào)用的設(shè)置器:
[myAccount setCurrentBalance:@(100.0)];
這是最直接的,但缺乏靈活性韭寸。 另一方面春哨,符合鍵值編碼的對(duì)象提供了一種更通用的機(jī)制,可以使用字符串標(biāo)識(shí)符訪問(wèn)對(duì)象的屬性恩伺。
1.1赴背、使用Key和KeyPaths徑識(shí)別對(duì)象的屬性
key是標(biāo)識(shí)特定屬性的字符串。 通常晶渠,按照約定凰荚,代表屬性的鍵是該屬性本身在代碼中出現(xiàn)的名稱。 key必須使用ASCII編碼褒脯,不能包含空格便瑟,并且通常以小寫(xiě)字母開(kāi)頭(盡管有例外,例如在許多類中找到的URL屬性)番川。
由于上面代碼中的BankAccount
類符合鍵值編碼到涂,因此它可以識(shí)別這些key鍵的owner
,currentBalance
和transactions
颁督,這是其屬性的名稱践啄。 您可以通過(guò)其鍵設(shè)置值,而不是調(diào)用setCurrentBalance:
方法:
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
1.2适篙、使用key獲取屬性的值
當(dāng)對(duì)象采用NSKeyValueCoding
協(xié)議時(shí)往核,它符合鍵值編碼。繼承自NSObject
的對(duì)象(提供了該協(xié)議的基本方法的默認(rèn)實(shí)現(xiàn))會(huì)自動(dòng)采用具有某些默認(rèn)行為的該協(xié)議嚷节。這樣的對(duì)象至少實(shí)現(xiàn)以下基于鍵的基本getter:
valueForKey:-返回由key參數(shù)命名的屬性的值聂儒。如果根據(jù)訪問(wèn)者搜索模式中描述的規(guī)則找不到由關(guān)鍵字命名的屬性,則該對(duì)象將向自身發(fā)送
valueForUndefinedKey:
消息硫痰。valueForUndefinedKey:
的默認(rèn)實(shí)現(xiàn)拋出了NSUndefinedKeyException
衩婚,但是子類可以重寫(xiě)此行為并更優(yōu)雅地處理這種情況。valueForKeyPath:-返回相對(duì)于接收者的指定密鑰路徑的值效斑。密鑰路徑序列中不符合特定鍵的鍵值編碼的任何對(duì)象(即
valueForKey:
的默認(rèn)實(shí)現(xiàn)無(wú)法找到訪問(wèn)器方法)都接收到valueForUndefinedKey:
消息非春。dictionaryWithValuesForKeys:-返回相對(duì)于接收者的鍵數(shù)組的值。該方法為數(shù)組中的每個(gè)鍵調(diào)用
valueForKey:
。返回的NSDictionary
包含數(shù)組中所有鍵的值奇昙。
集合對(duì)象(例如
NSArray
护侮,NSSet
和NSDictionary
)不能包含nil
作為值。 而是使用NSNull
對(duì)象表示nil
值储耐。NSNull
提供了單個(gè)實(shí)例羊初,表示對(duì)象屬性的nil
值。dictionaryWithValuesForKeys:
和相關(guān)的setValuesForKeysWithDictionary:
的默認(rèn)實(shí)現(xiàn)會(huì)自動(dòng)在NSNull
(在dictionary
參數(shù)中)和nil
(在存儲(chǔ)的屬性中)之間轉(zhuǎn)換什湘。
1.3长赞、使用key設(shè)置屬性的值
與getter
一樣,與鍵值編碼兼容的對(duì)象還根據(jù)NSObject
中提供的NSKeyValueCoding
協(xié)議的實(shí)現(xiàn)闽撤,為一小組具有默認(rèn)行為的定義setter:
setValue:forKey:
-將相對(duì)于接收消息的對(duì)象的指定鍵的值設(shè)置為給定值得哆。setValue:forKey:
的默認(rèn)實(shí)現(xiàn):將表示標(biāo)量和結(jié)構(gòu)的NSNumber
和NSValue
對(duì)象自動(dòng)解包,并將它們分配給屬性哟旗。如果該對(duì)象沒(méi)有對(duì)應(yīng)的key的屬性贩据,則該對(duì)象將向自身發(fā)送
setValue:forUndefinedKey:
消息。setValue:forUndefinedKey:
的默認(rèn)實(shí)現(xiàn)拋出一個(gè)NSUndefinedKeyException
闸餐。但是乐设,子類可以重寫(xiě)此方法以自定義方式處理請(qǐng)求。setValue:forKeyPath:
-在相對(duì)于接收者的指定鍵路徑處設(shè)置給定值绎巨。key路徑序列中不符合特定鍵的鍵值編碼的任何對(duì)象都會(huì)收到setValue:forUndefinedKey:
消息。setValuesForKeysWithDictionary:
-使用字典鍵標(biāo)識(shí)屬性蠕啄,使用指定字典中的值設(shè)置接收器的屬性场勤。默認(rèn)實(shí)現(xiàn)為每個(gè)鍵值對(duì)調(diào)用setValue:forKey :
,并根據(jù)需要用nil
代替NSNull
對(duì)象歼跟。
在默認(rèn)實(shí)現(xiàn)中和媳,當(dāng)您嘗試將非對(duì)象屬性設(shè)置為nil
值時(shí),符合鍵值編碼的對(duì)象會(huì)向自身發(fā)送setNilValueForKey:
消息哈街。 setNilValueForKey:
的默認(rèn)實(shí)現(xiàn)拋出NSInvalidArgumentException
留瞳,但是對(duì)象重寫(xiě)這個(gè)方法,以設(shè)置默認(rèn)值或標(biāo)記值骚秦。
2她倘、訪問(wèn)集合屬性
就像訪問(wèn)和設(shè)置其他屬性一樣,您也可以使用valueForKey:
和setValue:forKey:
來(lái)訪問(wèn)和設(shè)置集合屬性的值作箍。但是硬梁,當(dāng)您要操縱這些集合的內(nèi)容時(shí),通常使用協(xié)議定義的可變代理方法最有效胞得。
該協(xié)議定義了三種不同的代理對(duì)象訪問(wèn)代理方法荧止,每種方法都有一個(gè)鍵和一個(gè)鍵路徑變量:
mutableArrayValueForKey:
和mutableArrayValueForKeyPath:
它們返回行為類似于NSMutableArray
對(duì)象的代理對(duì)象。mutableSetValueForKey:
和mutableSetValueForKeyPath:
它們返回行為類似于NSMutableSet
對(duì)象的代理對(duì)象。mutableOrderedSetValueForKey:
和mutableOrderedSetValueForKeyPath:
它們返回行為類似于NSMutableOrderedSet
對(duì)象的代理象跃巡。
3危号、集合運(yùn)算符
當(dāng)您發(fā)送與鍵值編碼兼容的對(duì)象valueForKeyPath:
消息時(shí),可以將集合運(yùn)算符嵌入到鍵路徑中素邪。集合運(yùn)算符是一小部分關(guān)鍵字之一外莲,其后帶有一個(gè)at符號(hào)(@),該符號(hào)指定getter
在返回?cái)?shù)據(jù)之前應(yīng)執(zhí)行的操作以某種方式處理數(shù)據(jù)娘香。由NSObject
提供的valueForKeyPath:
的默認(rèn)實(shí)現(xiàn)會(huì)實(shí)現(xiàn)此行為苍狰。
-
@avg.屬性名
求集合中對(duì)象某個(gè)屬性的平均值。 -
@count
求集合中對(duì)象個(gè)數(shù)烘绽。 -
@max.屬性名
求集合中對(duì)象某個(gè)屬性的最大值淋昭。 -
@min.屬性名
求集合中對(duì)象某個(gè)屬性的最小值。 -
@sum.屬性名
求集合中對(duì)象某個(gè)屬性的和安接。 -
@distinctUnionOfObjects.屬性名
取出集合中所有對(duì)象某個(gè)屬性的值翔忽,并將這些值存入一個(gè)新的數(shù)組并返回。這個(gè)操作去重盏檐。 -
@unionOfObjects.屬性名
取出集合中所有對(duì)象某個(gè)屬性的值歇式,并將這些值存入一個(gè)新的數(shù)組并返回。這個(gè)操作不去重胡野。 -
@distinctUnionOfArrays.屬性名
取出嵌套集合(集合嵌套集合)中所有對(duì)象某個(gè)屬性的值材失,并返回一個(gè)新的數(shù)組。這個(gè)操作去重硫豆。 -
@unionOfArrays.屬性名
取出嵌套集合(集合嵌套集合)中所有對(duì)象某個(gè)屬性的值龙巨,并返回一個(gè)新的數(shù)組。這個(gè)操作不去重熊响。 -
@distinctUnionOfSets.屬性名
返回值是個(gè)一個(gè)NSSet
效果和distinctUnionOfArrays
一樣旨别。
4、類型轉(zhuǎn)換
當(dāng)您調(diào)用協(xié)議的一種getters汗茄,例如valueForKey:
時(shí)秸弛,默認(rèn)實(shí)現(xiàn)將根據(jù)訪問(wèn)者搜索模式中描述的規(guī)則來(lái)確定為指定鍵提供值的特定訪問(wèn)器方法或?qū)嵗兞俊?如果返回值不是對(duì)象,則getter使用此值初始化NSNumber
對(duì)象(用于標(biāo)量)或NSValue
對(duì)象(用于結(jié)構(gòu)體)洪碳,并返回該值递览。
類似地,默認(rèn)情況下偶宫,使用setValue:forKey
之類的setter:在給定特定鍵的情況下非迹,確定屬性的訪問(wèn)器或?qū)嵗兞克璧臄?shù)據(jù)類型。 如果數(shù)據(jù)類型不是對(duì)象纯趋,則設(shè)置器首先將適當(dāng)?shù)?code><type> Value消息發(fā)送到傳入值對(duì)象以提取基礎(chǔ)數(shù)據(jù)憎兽,然后存儲(chǔ)該數(shù)據(jù)冷离。
Data type | Creation method | Accessor method |
---|---|---|
BOOL |
numberWithBool: |
boolValue (in iOS) charValue (in macOS)* |
char |
numberWithChar: |
charValue |
double |
numberWithDouble: |
doubleValue |
float |
numberWithFloat: |
floatValue |
int |
numberWithInt: |
intValue |
long |
numberWithLong: |
longValue |
long long |
numberWithLongLong: |
longLongValue |
short |
numberWithShort: |
shortValue |
unsigned char |
numberWithUnsignedChar: |
unsignedChar |
unsigned int |
numberWithUnsignedInt: |
unsignedInt |
unsigned long |
numberWithUnsignedLong: |
unsignedLong |
unsigned long long |
numberWithUnsignedLongLong: |
unsignedLongLong |
unsigned short |
numberWithUnsignedShort: |
unsignedShort |
Data type | Creation method | Accessor method |
---|---|---|
NSPoint |
valueWithPoint: |
pointValue |
NSRange |
valueWithRange: |
rangeValue |
NSRect |
valueWithRect: (macOS only). |
rectValue |
NSSize |
valueWithSize: |
sizeValue |
4.1、自定義結(jié)構(gòu)體類型的轉(zhuǎn)換
typedef struct {
float x, y, z;
} ThreeFloats;
@interface MyClass
@property (nonatomic) ThreeFloats threeFloats;
@end
取值
NSValue* result = [myClass valueForKey:@"threeFloats"];
設(shè)置值
ThreeFloats floats = {1., 2., 3.};
NSValue* value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[myClass setValue:value forKey:@"threeFloats"];
5纯命、KVC的底層原理
5.1西剥、基本getter的搜索模式
當(dāng)一個(gè)對(duì)象調(diào)用valueForKey:
方法取值的時(shí)候,他的內(nèi)部執(zhí)行以下過(guò)程亿汞。
- 1.在實(shí)例中搜索找到具有名稱的第一個(gè)訪問(wèn)器方法
get<Key>瞭空,<key>,is<Key>疗我,或者_(dá)<key>咆畏,
按照這個(gè)順序。如果找到吴裤,則調(diào)用它并執(zhí)行步驟5旧找。否則,請(qǐng)繼續(xù)下一步麦牺。 - 2.判斷是否是數(shù)組钮蛛,如果是數(shù)組則對(duì)數(shù)組中的每個(gè)對(duì)象一次調(diào)用
valueForKey:
方法,并返回一個(gè)新的數(shù)組剖膳。否則就執(zhí)行步驟3魏颓。 - 3.判斷是否是
NSSet
,如果是集合則對(duì)集合中的每個(gè)對(duì)象一次調(diào)用valueForKey:
方法,并返回一個(gè)新的集合吱晒。否則就執(zhí)行步驟4甸饱。 - 4.調(diào)用
accessInstanceVariablesDirectly
方法,判斷是否啟用實(shí)例變量的查找仑濒,默認(rèn)是YES
柜候,也就是啟用,當(dāng)返回為YES
時(shí)躏精,將按照這個(gè)_<key>, _is<Key>, <key>, or is<Key>,
來(lái)一次查找。我們可以通過(guò)重寫(xiě)這個(gè)方法來(lái)禁用實(shí)例變量的查找鹦肿。 - 5.如果檢索到的屬性值是對(duì)象指針矗烛,則只需返回結(jié)果。如果該值是
NSNumber
支持的標(biāo)量類型箩溃,則將其存儲(chǔ)在NSNumber
實(shí)例中并返回它瞭吃。如果結(jié)果是NSNumber
不支持的標(biāo)量類型,請(qǐng)轉(zhuǎn)換為NSValue
對(duì)象并返回該對(duì)象涣旨。 - 6.如果所有的方法均失敗歪架,則調(diào)用
valueForUndefinedKey:
。 默認(rèn)情況下霹陡,這會(huì)拋出一個(gè)異常和蚪,但是NSObject
的子類可以通過(guò)重寫(xiě)這個(gè)方法止状,來(lái)定制一些特性的功能。
5.1攒霹、基本setter的搜索模式
setValue:forKey:
的默認(rèn)實(shí)現(xiàn)(給定鍵和值參數(shù)作為輸入)怯疤,嘗試將名為key的屬性設(shè)置為value,在使用這個(gè)方法設(shè)置值時(shí)催束,對(duì)象的內(nèi)部會(huì)經(jīng)歷以下流程集峦。
1.按該順序查找名為
set <Key>:或_set <Key>
的第一個(gè)訪問(wèn)器。 如果找到抠刺,請(qǐng)使用輸入值調(diào)用它并完成塔淤。2.如果沒(méi)有找到setter訪問(wèn)器,并且類方法
accessInstanceVariablesDirectly
返回YES速妖,則按該順序查找名稱類似于_ <key>高蜂,_ is <Key>,<key>或is <Key>
的實(shí)例變量买优。 如果找到妨马,直接用輸入值設(shè)置變量并完成操作。3.在找不到訪問(wèn)器或?qū)嵗兞亢笊庇{(diào)用
setValue:forUndefinedKey:
烘跺。 默認(rèn)情況下,這會(huì)拋出一個(gè)異常脂崔,但是NSObject的子類可以通過(guò)重寫(xiě)這個(gè)方法來(lái)提供特定的操作滤淳。