iOS KVC底層原理

什么是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鍵的ownercurrentBalancetransactions颁督,這是其屬性的名稱践啄。 您可以通過(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护侮,NSSetNSDictionary)不能包含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)的NSNumberNSValue對(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)提供特定的操作滤淳。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市砌左,隨后出現(xiàn)的幾起案子脖咐,更是在濱河造成了極大的恐慌,老刑警劉巖汇歹,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屁擅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡产弹,警方通過(guò)查閱死者的電腦和手機(jī)派歌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)痰哨,“玉大人胶果,你說(shuō)我怎么就攤上這事〗锔” “怎么了早抠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)撬讽。 經(jīng)常有香客問(wèn)我蕊连,道長(zhǎng)悬垃,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任咪奖,我火速辦了婚禮盗忱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘羊赵。我一直安慰自己趟佃,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布昧捷。 她就那樣靜靜地躺著闲昭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪靡挥。 梳的紋絲不亂的頭發(fā)上序矩,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音跋破,去河邊找鬼簸淀。 笑死,一個(gè)胖子當(dāng)著我的面吹牛毒返,可吹牛的內(nèi)容都是我干的租幕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拧簸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼劲绪!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起盆赤,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤贾富,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后牺六,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體颤枪,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年淑际,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了汇鞭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡庸追,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出台囱,到底是詐尸還是另有隱情淡溯,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布簿训,位于F島的核電站咱娶,受9級(jí)特大地震影響米间,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜膘侮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一屈糊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧琼了,春花似錦逻锐、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至所袁,卻和暖如春盏档,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背燥爷。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工蜈亩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人前翎。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓稚配,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鱼填。 傳聞我的和親對(duì)象是個(gè)殘疾皇子药有,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容