1. 簡(jiǎn)介
1.1 關(guān)于KVC
鍵值編碼是由NSKeyValueCoding非正式協(xié)議啟用的一種機(jī)制,對(duì)象采用該協(xié)議提供對(duì)其屬性的間接訪問(wèn)。當(dāng)一個(gè)對(duì)象與鍵值編碼兼容時(shí),它的屬性可以通過(guò)一個(gè)簡(jiǎn)潔、統(tǒng)一的消息傳遞接口通過(guò)字符串參數(shù)尋址。這種間接訪問(wèn)機(jī)制補(bǔ)充了實(shí)例變量及其相關(guān)訪問(wèn)器方法提供的直接訪問(wèn)。
通常使用訪問(wèn)器方法來(lái)訪問(wèn)對(duì)象的屬性。get訪問(wèn)器(或getter)返回屬性的值。set訪問(wèn)器(或setter)設(shè)置屬性的值。在Objective-C中,還可以直接訪問(wèn)屬性的底層實(shí)例變量。以上述任何一種方式訪問(wèn)對(duì)象屬性都很簡(jiǎn)單,但需要調(diào)用特定于屬性的方法或變量名。隨著屬性列表的增長(zhǎng)或更改,訪問(wèn)這些屬性的代碼也必須如此。相反,一個(gè)與鍵值編碼兼容的對(duì)象提供了一個(gè)簡(jiǎn)單的消息傳遞接口,該接口在其所有屬性中都是一致的。
鍵值編碼是許多其他Cocoa技術(shù)的基礎(chǔ)概念,例如KVO、Cocoa綁定、Core Data和AppleScript。在某些情況下,鍵值編碼也有助于簡(jiǎn)化代碼。
使用鍵值編碼兼容對(duì)象
對(duì)象從NSObject(直接或間接)繼承時(shí)通常采用鍵值編碼,NSObject既采用NSKeyValueCoding協(xié)議,又為基本方法提供默認(rèn)實(shí)現(xiàn)。此類對(duì)象使其他對(duì)象能夠通過(guò)壓縮消息傳遞接口執(zhí)行以下操作:
訪問(wèn)對(duì)象屬性。協(xié)議指定了一些方法,例如泛型gettervalueForKey:和泛型settersetValue:for key:,用于按名稱或鍵訪問(wèn)對(duì)象屬性,并將其參數(shù)化為字符串。這些方法和相關(guān)方法的默認(rèn)實(shí)現(xiàn)使用鍵來(lái)定位底層數(shù)據(jù)并與之交互,如訪問(wèn)對(duì)象屬性中所述。
操作集合屬性。訪問(wèn)方法的默認(rèn)實(shí)現(xiàn)與任何其他屬性一樣,使用對(duì)象的集合屬性(如NSArray對(duì)象)。此外,如果對(duì)象為屬性定義了集合訪問(wèn)器方法,則它將啟用對(duì)集合內(nèi)容的鍵值訪問(wèn)。這通常比直接訪問(wèn)更有效,并允許您通過(guò)標(biāo)準(zhǔn)化接口使用自定義集合對(duì)象。
對(duì)集合對(duì)象調(diào)用集合運(yùn)算符。在鍵值編碼兼容對(duì)象中訪問(wèn)集合屬性時(shí),可以將集合運(yùn)算符插入到鍵字符串中,如使用集合運(yùn)算符中所述。集合運(yùn)算符默認(rèn)的NSKeyValueCoding getter實(shí)現(xiàn)對(duì)集合執(zhí)行操作,然后返回集合的新篩選版本或表示集合某些特征的單個(gè)值。
訪問(wèn)非對(duì)象屬性。協(xié)議的默認(rèn)實(shí)現(xiàn)檢測(cè)非對(duì)象屬性,包括標(biāo)量和結(jié)構(gòu),并自動(dòng)將它們作為對(duì)象包裝和展開,以便在協(xié)議接口上使用。此外,協(xié)議聲明了一個(gè)方法,當(dāng)通過(guò)鍵值編碼接口在非對(duì)象屬性上設(shè)置nil值時(shí),允許兼容對(duì)象為這種情況提供合適的操作。
按key path訪問(wèn)屬性。當(dāng)您有一個(gè)與鍵值編碼兼容的對(duì)象的層次結(jié)構(gòu)時(shí),您可以使用基于鍵值路徑的方法調(diào)用來(lái)向下鉆取、獲取或設(shè)置層次結(jié)構(gòu)中使用單個(gè)調(diào)用的深層值。
對(duì)象采用鍵值編碼
為了使您自己的對(duì)象鍵值編碼兼容,您需要確保它們采用NSKeyValueCoding非正式協(xié)議并實(shí)現(xiàn)相應(yīng)的方法,例如value forKey:和setValue:forKey:。幸運(yùn)的是,NSObject采用此協(xié)議并為這些方法和其他基本方法提供默認(rèn)實(shí)現(xiàn)。因此,如果從NSObject(或其許多子類中的任何一個(gè)子類)派生對(duì)象,那么大部分工作已經(jīng)為您完成了。
為了讓默認(rèn)方法完成它們的工作,需要確保對(duì)象的訪問(wèn)器方法和實(shí)例變量遵循某些定義良好的模式。這允許默認(rèn)實(shí)現(xiàn)查找對(duì)象的屬性以響應(yīng)鍵值編碼的消息。然后,您可以選擇通過(guò)提供用于驗(yàn)證和處理某些特殊情況的方法來(lái)擴(kuò)展和自定義鍵值編碼。
用Swift進(jìn)行鍵值編碼
默認(rèn)情況下,繼承自NSObject或其子類之一的Swift對(duì)象是符合其屬性的鍵值編碼。在Objective-C中,屬性的訪問(wèn)器和實(shí)例變量必須遵循某些模式,而Swift中的標(biāo)準(zhǔn)屬性聲明會(huì)自動(dòng)保證這一點(diǎn)。另一方面,協(xié)議的許多功能要么不相關(guān),要么使用Objective-C中不存在的本地Swift構(gòu)造或技術(shù)更好地處理。例如,由于所有Swift屬性都是對(duì)象,因此您永遠(yuǎn)不會(huì)對(duì)非對(duì)象屬性執(zhí)行默認(rèn)實(shí)現(xiàn)的特殊處理。
因此,雖然鍵值編碼協(xié)議方法直接轉(zhuǎn)換為Swift,但本指南主要關(guān)注Objective-C,在這里您需要做更多的工作來(lái)確保遵從性,并且鍵值編碼通常最有用。需要在Swift中采用顯著不同方法的情況在本指南中均有說(shuō)明。
有關(guān)將Swift與Cocoa技術(shù)結(jié)合使用的更多信息,請(qǐng)閱讀將Swift與Cocoa和Objective-C結(jié)合使用(Swift 3)。有關(guān)Swift的完整描述,請(qǐng)閱讀Swift編程語(yǔ)言(Swift 3)。
其他可可技術(shù)依賴于關(guān)鍵值編碼
符合關(guān)鍵值編碼的對(duì)象可以參與各種依賴于這種訪問(wèn)的Cocoa技術(shù),包括:
KVO。此機(jī)制使對(duì)象能夠注冊(cè)由另一個(gè)對(duì)象的屬性更改驅(qū)動(dòng)的異步通知。
Cocoa 綁定。這些技術(shù)的集合完全實(shí)現(xiàn)了MVC范式,其中模型封裝應(yīng)用程序數(shù)據(jù),視圖顯示和編輯數(shù)據(jù),控制器在兩者之間進(jìn)行中介。
Core Data。該框架為與對(duì)象生命周期和對(duì)象圖管理(包括持久性)相關(guān)聯(lián)的常見任務(wù)提供了通用和自動(dòng)化的解決方案。您可以閱讀核心數(shù)據(jù)編程指南中的核心數(shù)據(jù)。
AppleScript。這種腳本語(yǔ)言可以直接控制可腳本化的應(yīng)用程序和macOS的許多部分。Cocoa的腳本支持利用鍵值編碼來(lái)獲取和設(shè)置可腳本對(duì)象中的信息。NSScriptKeyValueCoding非正式協(xié)議中的方法提供了處理鍵值編碼的附加功能,包括通過(guò)多值鍵中的索引獲取和設(shè)置鍵值,以及將鍵值強(qiáng)制(或轉(zhuǎn)換)為適當(dāng)?shù)臄?shù)據(jù)類型。AppleScript概述提供了AppleScript及其相關(guān)技術(shù)的高級(jí)概述。
2. KVC基礎(chǔ)
2.1訪問(wèn)對(duì)象屬性
對(duì)象通常在其接口聲明中指定屬性,這些屬性屬于以下類別之一:
屬性。這些是簡(jiǎn)單的值,例如標(biāo)量、字符串或布爾值。值對(duì)象(如NSNumber)和其他不可變類型(如NSColor)也被視為屬性。
一對(duì)一關(guān)系。這些是具有自身屬性的可變對(duì)象。對(duì)象的屬性可以在不更改對(duì)象本身的情況下更改。例如,銀行帳戶對(duì)象可能具有所有者屬性,該屬性是Person對(duì)象的實(shí)例,而Person對(duì)象本身具有地址屬性。業(yè)主的地址可在不更改銀行賬戶所持業(yè)主證明的情況下更改。銀行帳戶的所有者沒有改變。只有他們的地址。
一對(duì)多關(guān)系。這些是集合對(duì)象。您通常使用NSArray或NSSet的實(shí)例來(lái)保存此類集合,但也可以使用自定義集合類。
@interface BankAccount : NSObject
@property (nonatomic) NSNumber* currentBalance; // An attribute
@property (nonatomic) Person* owner; // A to-one relation
@property (nonatomic) NSArray< Transaction* >* transactions; // A to-many relation
@end
為了維護(hù)封裝,對(duì)象通常為其接口上的屬性提供訪問(wèn)器方法。對(duì)象的作者可以顯式地編寫這些方法,也可以依賴編譯器自動(dòng)合成它們。無(wú)論如何,使用這些訪問(wèn)器之一的代碼的作者必須在編譯代碼之前將屬性名寫入代碼。訪問(wèn)器方法的名稱成為使用它的代碼的靜態(tài)部分。例如,BankAccount對(duì)象,編譯器將合成一個(gè)setter,您可以為myAccount實(shí)例調(diào)用它:
[myAccount setCurrentBalance:@(100.0)];
這是直接的,但缺乏靈活性。另一方面,與鍵值編碼兼容的對(duì)象提供了一種更通用的機(jī)制來(lái)使用字符串標(biāo)識(shí)符訪問(wèn)對(duì)象的屬性。
用鍵和鍵路徑標(biāo)識(shí)對(duì)象的屬性
鍵是標(biāo)識(shí)特定屬性的字符串。通常,根據(jù)約定,表示屬性的鍵是在代碼中出現(xiàn)的屬性本身的名稱。密鑰必須使用ASCII編碼,不能包含空格,并且通常以小寫字母開頭(盡管有例外,例如在許多類中找到的URL屬性)。
因?yàn)?code>BankAccount類是符合鍵值編碼的,所以它可以識(shí)別出owner、currentBalance和transactions,這是它的屬性名。您不必調(diào)用setCurrentBalance:方法,而是可以通過(guò)其鍵設(shè)置值:
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
實(shí)際上,可以使用不同的鍵參數(shù),用同一方法設(shè)置myAccount對(duì)象的所有屬性。因?yàn)閰?shù)是字符串,所以它可以是在運(yùn)行時(shí)操作的變量。
鍵路徑是.分隔鍵的字符串,用于指定要遍歷的對(duì)象屬性序列。序列中第一個(gè)鍵的屬性是相對(duì)于接收器的,并且每個(gè)后續(xù)鍵都是相對(duì)于前一個(gè)屬性的值計(jì)算的。鍵路徑對(duì)于使用單個(gè)方法調(diào)用向下鉆取對(duì)象的層次結(jié)構(gòu)非常有用。
例如,應(yīng)用于銀行帳戶實(shí)例的鍵路徑owner.address.street引用存儲(chǔ)在銀行帳戶所有者地址中的街道字符串的值,假設(shè)Person和address類也與鍵值編碼兼容。
使用鍵獲取屬性值
當(dāng)一個(gè)對(duì)象采用NSKeyValueCoding協(xié)議時(shí),它是與鍵值編碼兼容的。一個(gè)繼承自NSObject的對(duì)象,它提供了協(xié)議基本方法的默認(rèn)實(shí)現(xiàn),自動(dòng)采用該協(xié)議并具有某些默認(rèn)行為。這樣的對(duì)象至少實(shí)現(xiàn)以下基本的基于鍵的getter:
valueForKey:返回由鍵參數(shù)命名的屬性的值。如果根據(jù)訪問(wèn)器搜索模式中描述的規(guī)則找不到由鍵命名的屬性,則對(duì)象會(huì)向自身發(fā)送一個(gè)
valueForUndefinedKey:message。valueForUndefinedKey的默認(rèn)實(shí)現(xiàn):引發(fā)一個(gè)NSUndefinedKeyException,但是子類可以重寫此行為并更優(yōu)雅地處理這種情況。valueForKeyPath:返回相對(duì)于接收器的指定鍵路徑的值。鍵路徑序列中不符合特定鍵的鍵值編碼的任何對(duì)象,即valueForKey的默認(rèn)實(shí)現(xiàn)找不到訪問(wèn)器方法,會(huì)發(fā)出
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:自動(dòng)在NSNull(在dictionary參數(shù)中)和nil(在存儲(chǔ)的屬性中)之間轉(zhuǎn)換。
當(dāng)使用鍵路徑來(lái)尋址屬性時(shí),如果鍵路徑中除最后一個(gè)鍵之外的任何鍵是多對(duì)多關(guān)系(即,它引用一個(gè)集合),則返回值是一個(gè)集合,其中包含多對(duì)多鍵右側(cè)的鍵的所有值。例如,請(qǐng)求鍵路徑transactions.payee的值將返回一個(gè)包含所有事務(wù)的所有受款人對(duì)象的數(shù)組。這也適用于鍵路徑中的多個(gè)數(shù)組。鍵路徑accounts.transactions.payee返回一個(gè)數(shù)組,其中包含所有帳戶中所有事務(wù)的所有受款人對(duì)象。
使用鍵設(shè)置屬性值
與getter一樣,與鍵值編碼兼容的對(duì)象還根據(jù)NSObject中的NSKeyValueCoding協(xié)議的實(shí)現(xiàn),通用setter提供默認(rèn)行為:
-
setValue:forKey: 將指定鍵相對(duì)于接收消息的對(duì)象的值設(shè)置為給定值。
setValue:forKey:的默認(rèn)實(shí)現(xiàn):自動(dòng)展開表示標(biāo)量和結(jié)構(gòu)的NSNumber和NSValue對(duì)象,并將它們分配給屬性。如果指定的鍵對(duì)應(yīng)于接收setter調(diào)用的對(duì)象不具有的屬性,則該對(duì)象會(huì)向自身發(fā)送
setValue:forUndefinedKey:消息。setValue:forUndefinedKey:的默認(rèn)實(shí)現(xiàn)引發(fā)了NSUndefinedKeyException。但是,子類可以重寫此方法以自定義方式處理請(qǐng)求。 setValue:forKeyPath: 在相對(duì)于接收器的指定鍵路徑處設(shè)置給定值。鍵路徑序列中不符合特定鍵的鍵值編碼的任何對(duì)象都將接收
setValue:forUndefinedKey:消息。setValuesForKeysWithDictionary: 使用指定字典中的值設(shè)置接收器的屬性,使用字典鍵標(biāo)識(shí)屬性。默認(rèn)實(shí)現(xiàn)為每個(gè)鍵值對(duì)調(diào)用
setValue:forFey:,根據(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):引發(fā)NSInvalidArgumentException,但對(duì)象可以重寫此行為以替換默認(rèn)值或標(biāo)記值。
使用鍵簡(jiǎn)化對(duì)象訪問(wèn)
要了解基于鍵的getter和setter如何簡(jiǎn)化代碼,請(qǐng)考慮以下示例。在macOS中,NSTableView和NSOutlineView對(duì)象將標(biāo)識(shí)符字符串與它們的每一列相關(guān)聯(lián)。如果支持表的模型對(duì)象不符合鍵值編碼,則表的數(shù)據(jù)源方法將被迫依次檢查每個(gè)列標(biāo)識(shí)符,以找到要返回的正確屬性。此外,在將來(lái)向模型中添加另一個(gè)屬性(在本例中為Person對(duì)象)時(shí),還必須重新訪問(wèn)數(shù)據(jù)源方法,添加另一個(gè)條件以測(cè)試新屬性并返回相關(guān)值。
- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row
{
id result = nil;
Person *person = [self.people objectAtIndex:row];
if ([[column identifier] isEqualToString:@"name"]) {
result = [person name];
} else if ([[column identifier] isEqualToString:@"age"]) {
result = @([person age]); // Wrap age, a scalar, as an NSNumber
} else if ([[column identifier] isEqualToString:@"favoriteColor"]) {
result = [person favoriteColor];
} // And so on...
return result;
}
下面的代碼為同一個(gè)數(shù)據(jù)源方法體統(tǒng)了一個(gè)更加緊湊的實(shí)現(xiàn)。該方法利用了一個(gè)與鍵值編碼兼容的Person對(duì)象。僅使用valueForKey:getter,數(shù)據(jù)源方法使用列標(biāo)識(shí)符作為鍵返回適當(dāng)?shù)闹?。除了更短之外,它還更通用,因?yàn)樵谝院筇砑有铝袝r(shí),只要列標(biāo)識(shí)符始終與模型對(duì)象的屬性名稱匹配,它將繼續(xù)保持不變的工作。
- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row
{
return [[self.people objectAtIndex:row] valueForKey:[column identifier]];
}
2.2 訪問(wèn)集合屬性
可以像使用valueForKey:和setValue:forKey:(或它們的等價(jià)鍵路徑)一樣獲取或設(shè)置集合對(duì)象。但是,當(dāng)您想要操作這些集合的內(nèi)容時(shí),通常使用協(xié)議定義的可變代理方法是最有效的。
協(xié)議為集合對(duì)象訪問(wèn)定義了三種不同的代理方法,每種方法都有一個(gè)鍵和一個(gè)鍵路徑變量:
-
mutableArrayValueForKey:和mutableArrayValueForKeyPath:它們返回一個(gè)代理對(duì)象,其行為類似于NSMutableArray對(duì)象。
-
mutableSetValueForKey:和mutableSetValueForKeyPath:它們返回一個(gè)代理對(duì)象,其行為類似于NSMutableSet對(duì)象。
-
mutableOrderedSetValueForKey:和mutableOrderedSetValueForKeyPath:它們返回一個(gè)代理對(duì)象,其行為類似于NSMutableOrderedSet對(duì)象。
在代理對(duì)象上操作、向其中添加對(duì)象、從中刪除對(duì)象或替換其中的對(duì)象時(shí),協(xié)議的默認(rèn)實(shí)現(xiàn)會(huì)相應(yīng)地修改基礎(chǔ)屬性。這比使用valueForKey:獲取不可變的集合對(duì)象、使用更改的內(nèi)容創(chuàng)建已修改的集合對(duì)象,然后使用setValue:forKey:消息將其存儲(chǔ)回對(duì)象更有效。在許多情況下,它也比直接使用可變屬性更有效。這些方法還提供了一個(gè)額外的好處,即為collection對(duì)象中保存的對(duì)象保持鍵值觀測(cè)遵從性。
2.3 使用集合運(yùn)算符
當(dāng)發(fā)送符合鍵值編碼的對(duì)象valueForKeyPath:消息時(shí),可以在鍵值路徑中嵌入集合運(yùn)算符。集合運(yùn)算符是前面有at符號(hào)(@)的一個(gè)小關(guān)鍵字列表,該符號(hào)指定getter在返回?cái)?shù)據(jù)之前應(yīng)執(zhí)行的操作,以便以某種方式操作數(shù)據(jù)。由NSObject提供的valueForKeyPath的默認(rèn)實(shí)現(xiàn)實(shí)現(xiàn)了此行為。
當(dāng)鍵路徑包含集合運(yùn)算符時(shí),該運(yùn)算符之前的鍵路徑的任何部分(稱為左鍵路徑)指示相對(duì)于消息的接收者要對(duì)其進(jìn)行操作的集合。如果將消息直接發(fā)送到集合對(duì)象(如NSArray實(shí)例),則可以省略左鍵路徑。
運(yùn)算符后面的鍵路徑部分(稱為右鍵路徑)指定運(yùn)算符應(yīng)處理的集合內(nèi)的屬性。除@count之外的所有集合運(yùn)算符都需要一個(gè)正確的鍵路徑。

集合運(yùn)算符顯示三種基本類型的行為:
聚合運(yùn)算符以某種方式合并集合的對(duì)象,并返回通常與右鍵路徑中命名的屬性的數(shù)據(jù)類型匹配的單個(gè)對(duì)象。
@count運(yùn)算符是一個(gè)例外,它不接受右鍵路徑,并且總是返回NSNumber實(shí)例。數(shù)組運(yùn)算符返回一個(gè)NSArray實(shí)例,該實(shí)例包含命名集合中保存的某些對(duì)象子集。
嵌套運(yùn)算符處理包含其他集合的集合,并返回NSArray或NSSet實(shí)例(取決于運(yùn)算符),該實(shí)例以某種方式組合嵌套集合的對(duì)象。
樣本數(shù)據(jù)
示例數(shù)據(jù)后面的描述包括演示如何調(diào)用每個(gè)運(yùn)算符的代碼片段,以及調(diào)用的結(jié)果。BankAccount類,該類包含一個(gè)Transaction對(duì)象數(shù)組。每一個(gè)都代表一個(gè)簡(jiǎn)單的支票簿條目。
@interface BankAccount : NSObject
@property (nonatomic) NSNumber* currentBalance; // An attribute
@property (nonatomic) Person* owner; // A to-one relation
@property (nonatomic) NSArray< Transaction* >* transactions; // A to-many relation
@end
@interface Transaction : NSObject
@property (nonatomic) NSString* payee; // To whom
@property (nonatomic) NSNumber* amount; // How much
@property (nonatomic) NSDate* date; // When
@end
為了便于討論,假設(shè)BankAccount實(shí)例有一個(gè)用下表所示數(shù)據(jù)填充的事務(wù)數(shù)組,并且從BankAccount對(duì)象內(nèi)部進(jìn)行了示例調(diào)用。
payee values |
amount values formatted as currency |
date values formatted as month day, year |
|---|---|---|
Green Power |
$120.00 |
Dec 1, 2015 |
Green Power |
$150.00 |
Jan 1, 2016 |
Green Power |
$170.00 |
Feb 1, 2016 |
Car Loan |
$250.00 |
Jan 15, 2016 |
Car Loan |
$250.00 |
Feb 15, 2016 |
Car Loan |
$250.00 |
Mar 15, 2016 |
General Cable |
$120.00 |
Dec 1, 2015 |
General Cable |
$155.00 |
Jan 1, 2016 |
General Cable |
$120.00 |
Feb 1, 2016 |
Mortgage |
$1,250.00 |
Jan 15, 2016 |
Mortgage |
$1,250.00 |
Feb 15, 2016 |
Mortgage |
$1,250.00 |
Mar 15, 2016 |
Animal Hospital |
$600.00 |
Jul 15, 2016 |
聚合運(yùn)算符
聚合運(yùn)算符處理一個(gè)數(shù)組或一組屬性,生成反映集合某個(gè)方面的單個(gè)值。
@avg
指定@avg運(yùn)算符時(shí),valueForKeyPath:讀取由集合中每個(gè)元素的右鍵路徑指定的屬性,將其轉(zhuǎn)換為double(用0替換nil值),并計(jì)算這些值的算術(shù)平均值。然后返回存儲(chǔ)在NSNumber實(shí)例中的結(jié)果。
NSNumber *transactionAverage = [self.transactions valueForKeyPath:@"@avg.amount"];
//結(jié)果為transactionAverage = $456.54
@count
指定@count運(yùn)算符時(shí),valueForKeyPath:返回NSNumber實(shí)例中集合中的對(duì)象數(shù)。右鍵路徑(如果存在)將被忽略。
NSNumber *numberOfTransactions = [self.transactions valueForKeyPath:@"@count"];
//結(jié)果為numberOfTransactions = 13
@max
指定@max運(yùn)算符時(shí),valueForKeyPath:在由右鍵路徑命名的集合項(xiàng)中搜索并返回最大的一個(gè)。搜索使用compare:方法進(jìn)行比較,該方法由許多基礎(chǔ)類(如NSNumber類)定義。因此,右鍵路徑指示的屬性必須包含對(duì)該消息有意義響應(yīng)的對(duì)象。搜索將忽略值為零的集合項(xiàng)。
NSDate *latestDate = [self.transactions valueForKeyPath:@"@max.date"];
//結(jié)果為latestDate = Jul 15, 2016.
@min
指定@min運(yùn)算符時(shí),valueForKeyPath:在由右鍵路徑命名的集合項(xiàng)中搜索并返回最小的集合項(xiàng)。搜索使用compare:方法進(jìn)行比較,該方法由許多基礎(chǔ)類(如NSNumber類)定義。因此,右鍵路徑指示的屬性必須包含對(duì)該消息有意義響應(yīng)的對(duì)象。搜索將忽略值為零的集合項(xiàng)。
NSDate *earliestDate = [self.transactions valueForKeyPath:@"@min.date"];
//結(jié)果為earliestDate = Dec 1, 2015.
@sum
指定@sum運(yùn)算符時(shí),valueForKeyPath:讀取由集合中每個(gè)元素的右鍵路徑指定的屬性,將其轉(zhuǎn)換為double(用0替換nil值),并計(jì)算這些值的和。然后返回存儲(chǔ)在NSNumber實(shí)例中的結(jié)果。
NSNumber *amountSum = [self.transactions valueForKeyPath:@"@sum.amount"];
//結(jié)果為amountSum = $5,935.00
數(shù)組運(yùn)算符
數(shù)組運(yùn)算符導(dǎo)致valueForKeyPath:返回一個(gè)對(duì)象數(shù)組,該數(shù)組對(duì)應(yīng)于由右鍵路徑指示的特定對(duì)象集。
注意:使用數(shù)組運(yùn)算符時(shí),如果任何葉對(duì)象為零,valueForKeyPath:方法將引發(fā)異常。
@distinctUnionOfObjects
指定@distinctUnionOfObjects運(yùn)算符時(shí),valueForKeyPath:創(chuàng)建并返回一個(gè)數(shù)組,該數(shù)組包含與右鍵路徑指定的屬性相對(duì)應(yīng)的集合的不同對(duì)象。
NSArray *distinctPayees = [self.transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"];
//結(jié)果為[Car Loan, General Cable, Animal Hospital, Green Power, Mortgage]
@unionOfObjects
指定@unionOfObjects運(yùn)算符時(shí),valueForKeyPath:創(chuàng)建并返回一個(gè)數(shù)組,該數(shù)組包含與右鍵路徑指定的屬性相對(duì)應(yīng)的集合的所有對(duì)象。與@distinctUnionOfObjects不同,不會(huì)刪除重復(fù)的對(duì)象。
NSArray *payees = [self.transactions valueForKeyPath:@"@unionOfObjects.payee"];
//結(jié)果為[Green Power, Green Power, Green Power, Car Loan, Car Loan, Car Loan, General Cable, General Cable, General Cable, Mortgage, Mortgage, Mortgage, Animal Hospital]
嵌套運(yùn)算符
嵌套運(yùn)算符對(duì)嵌套集合進(jìn)行操作,集合的每個(gè)項(xiàng)本身都包含一個(gè)集合。
注意:使用嵌套運(yùn)算符時(shí),如果任何葉對(duì)象為零,valueForKeyPath:方法將引發(fā)異常。
對(duì)于下面的描述,請(qǐng)考慮另一個(gè)名為moreTransactions的數(shù)據(jù)數(shù)組,該數(shù)組由下表中的數(shù)據(jù)填充,并與原始事務(wù)數(shù)組一起收集到一個(gè)嵌套數(shù)組中:
NSArray* moreTransactions = @[<# transaction data #>];
NSArray* arrayOfArrays = @[self.transactions, moreTransactions];
payee values |
amount values formatted as currency |
date values formatted as month day, year |
|---|---|---|
General Cable - Cottage |
$120.00 |
Dec 18, 2015 |
General Cable - Cottage |
$155.00 |
Jan 9, 2016 |
General Cable - Cottage |
$120.00 |
Dec 1, 2016 |
Second Mortgage |
$1,250.00 |
Nov 15, 2016 |
Second Mortgage |
$1,250.00 |
Sep 20, 2016 |
Second Mortgage |
$1,250.00 |
Feb 12, 2016 |
Hobby Shop |
$600.00 |
Jun 14, 2016 |
@distinctUnionOfArrays
指定@distinctUnionOfArrays運(yùn)算符時(shí),valueForKeyPath:創(chuàng)建并返回一個(gè)數(shù)組,該數(shù)組包含與右鍵路徑指定的屬性相對(duì)應(yīng)的所有集合組合的不同對(duì)象。
NSArray *collectedDistinctPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"];
//結(jié)果為[Hobby Shop, Mortgage, Animal Hospital, Second Mortgage, Car Loan, General Cable - Cottage, General Cable, Green Power]
@unionOfArrays
指定@unionOfArrays運(yùn)算符時(shí),valueForKeyPath:創(chuàng)建并返回一個(gè)數(shù)組,該數(shù)組包含與右鍵路徑指定的屬性相對(duì)應(yīng)的所有集合組合的所有對(duì)象,而不刪除重復(fù)項(xiàng)。
NSArray *collectedPayees = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.payee"];
//結(jié)果為[Green Power, Green Power, Green Power, Car Loan, Car Loan, Car Loan, General Cable, General Cable, General Cable, Mortgage, Mortgage, Mortgage, Animal Hospital, General Cable - Cottage, General Cable - Cottage, General Cable - Cottage, Second Mortgage, Second Mortgage, Second Mortgage, Hobby Shop]
@distinctUnionOfSets
指定@distinctUnionOfSets運(yùn)算符時(shí),valueForKeyPath:創(chuàng)建并返回一個(gè)NSSet對(duì)象,該對(duì)象包含與右鍵路徑指定的屬性相對(duì)應(yīng)的所有集合的組合的不同對(duì)象。
此運(yùn)算符的行為與@distinctUnionOfArrays類似,只是它希望NSSet實(shí)例包含對(duì)象的NSSet實(shí)例,而不是NSArray實(shí)例的NSArray實(shí)例。此外,它還返回一個(gè)NSSet實(shí)例。假設(shè)示例數(shù)據(jù)存儲(chǔ)在集合而不是數(shù)組中,則示例調(diào)用和結(jié)果與@distinctUnionOfArrays中顯示的調(diào)用和結(jié)果相同。
2.4 表示非對(duì)象值
NSObject提供的鍵值編碼協(xié)議方法的默認(rèn)實(shí)現(xiàn)同時(shí)使用對(duì)象和非對(duì)象屬性。默認(rèn)實(shí)現(xiàn)在對(duì)象參數(shù)或返回值與非對(duì)象屬性之間自動(dòng)轉(zhuǎn)換。這允許基于鍵的getter和setter的簽名保持一致,即使存儲(chǔ)的屬性是標(biāo)量或結(jié)構(gòu)。
注意:因?yàn)镾wift中的所有屬性都是對(duì)象,所以本節(jié)只適用于Objective-C屬性。
當(dāng)您調(diào)用協(xié)議的一個(gè)getter(如valueForKey:)時(shí),默認(rèn)實(shí)現(xiàn)將根據(jù)訪問(wèn)器搜索模式中描述的規(guī)則確定為指定鍵提供值的特定訪問(wèn)器方法或?qū)嵗兞俊?/p>
如果返回值不是對(duì)象,則getter使用此值初始化NSNumber對(duì)象(用于標(biāo)量)或NSValue對(duì)象(用于結(jié)構(gòu))并返回該值。
類似地,在默認(rèn)情況下,setValue:forKey:這樣的setter確定給定特定鍵的屬性訪問(wèn)器或?qū)嵗兞克璧臄?shù)據(jù)類型。如果數(shù)據(jù)類型不是對(duì)象,則setter首先向傳入的Value對(duì)象發(fā)送適當(dāng)?shù)?code><type>Value消息以提取底層數(shù)據(jù),并存儲(chǔ)該數(shù)據(jù)。
注意:當(dāng)您使用非對(duì)象屬性的nil值調(diào)用一個(gè)鍵值編碼協(xié)議setter時(shí),setter沒有明顯的、通用的操作過(guò)程。因此,它向接收setter調(diào)用的對(duì)象發(fā)送setNilValueForKey:消息。此方法的默認(rèn)實(shí)現(xiàn)引發(fā)NSInvalidArgumentException異常,但子類可能重寫此行為,如處理非對(duì)象值(例如設(shè)置標(biāo)記值或提供有意義的默認(rèn)值)中所述。
包裝和展開標(biāo)量類型
下表列出了默認(rèn)鍵值編碼實(shí)現(xiàn)使用NSNumber實(shí)例包裝的標(biāo)量類型。對(duì)于每個(gè)數(shù)據(jù)類型,該表顯示了用于從基礎(chǔ)屬性值初始化NSNumber以提供getter返回值的創(chuàng)建方法。然后顯示在set操作期間用于從setter輸入?yún)?shù)提取值的訪問(wèn)器方法。
| 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 |
注意:*在macOS中,由于歷史原因,BOOL被定義為帶符號(hào)的char類型,KVC不區(qū)分這兩種類型。因此,當(dāng)鍵為BOOL時(shí),不應(yīng)將諸如@“true”或@“YES”之類的字符串值傳遞給setValue:forKey:。KVC將嘗試調(diào)用charValue(因?yàn)锽OOL本身就是一個(gè)char),但是NSString沒有實(shí)現(xiàn)這個(gè)方法,這會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤。相反,當(dāng)鍵是BOOL時(shí),只將NSNumber對(duì)象(如@(1)或@(YES))作為值參數(shù)傳遞給setValue:forKey:。此限制不適用于iOS,在iOS中,BOOL是定義為本機(jī)布爾類型BOOL的類型,KVC調(diào)用boolValue,它適用于NSNumber對(duì)象或格式正確的NSString對(duì)象。
包裝和拆封結(jié)構(gòu)
下表顯示了默認(rèn)訪問(wèn)器用于包裝和展開常用NSPoint、NSRange、NSRect和NSSize結(jié)構(gòu)的創(chuàng)建和訪問(wèn)器方法。
| Data type | Creation method | Accessor method |
|---|---|---|
NSPoint |
valueWithPoint: |
pointValue |
NSRange |
valueWithRange: |
rangeValue |
NSRect |
valueWithRect: (macOS only). |
rectValue |
NSSize |
valueWithSize: |
sizeValue |
自動(dòng)包裝和展開不限于NSPoint、NSRange、NSRect和NSSize。結(jié)構(gòu)類型(即Objective-C類型編碼字符串以{開頭的類型)可以包裝在NSValue對(duì)象中。
typedef struct {
float x, y, z;
} ThreeFloats;
@interface MyClass
@property (nonatomic) ThreeFloats threeFloats;
@end
使用這個(gè)類的一個(gè)名為myClass的實(shí)例,您可以獲得帶有鍵值編碼的threeFloats值:
NSValue* result = [myClass valueForKey:@"threeFloats"];
valueForKey的默認(rèn)實(shí)現(xiàn):調(diào)用threeFloatsgetter,然后返回包裝在NSValue對(duì)象中的結(jié)果。
類似地,可以使用鍵值編碼設(shè)置threeFloats值:
ThreeFloats floats = {1., 2., 3.};
NSValue* value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[myClass setValue:value forKey:@"threeFloats"];
默認(rèn)實(shí)現(xiàn)使用getValue:消息來(lái)展開值,然后使用結(jié)果結(jié)構(gòu)調(diào)用setThreeFloats:。
2.5 驗(yàn)證屬性
鍵值編碼協(xié)議定義了支持屬性驗(yàn)證的方法。和使用基于鍵的訪問(wèn)器來(lái)讀寫符合鍵值編碼的對(duì)象的屬性一樣,也可以通過(guò)鍵(或鍵路徑)來(lái)驗(yàn)證屬性。調(diào)用validateValue:forKey:error:(或validateValue:forKeyPath:error:)方法時(shí),協(xié)議的默認(rèn)實(shí)現(xiàn)將搜索接收驗(yàn)證消息的對(duì)象(或鍵路徑末尾的對(duì)象),以查找名稱與模式validate<Key>:error:匹配的方法。如果對(duì)象沒有此類方法,則默認(rèn)情況下驗(yàn)證成功,默認(rèn)實(shí)現(xiàn)返回YES。如果存在特定于屬性的驗(yàn)證方法,則默認(rèn)實(shí)現(xiàn)將返回調(diào)用該方法的結(jié)果。
注意:您通常只使用Objective-C中描述的驗(yàn)證。在Swift中,屬性驗(yàn)證更習(xí)慣于通過(guò)依賴編譯器對(duì)選項(xiàng)和強(qiáng)類型檢查的支持來(lái)處理,而使用內(nèi)置的willSet和didSet屬性觀察程序來(lái)測(cè)試任何運(yùn)行時(shí)API協(xié)定,如Swift編程語(yǔ)言(Swift 3)。
由于特定于屬性的驗(yàn)證方法通過(guò)引用接收值和錯(cuò)誤參數(shù),因此驗(yàn)證有三種可能的結(jié)果:
- 驗(yàn)證方法認(rèn)為值對(duì)象有效,并在不更改值或錯(cuò)誤的情況下返回YES。
- 驗(yàn)證方法認(rèn)為值對(duì)象無(wú)效,但選擇不更改它。在這種情況下,該方法返回NO并將錯(cuò)誤引用(如果由調(diào)用方提供)設(shè)置為指示失敗原因的NSError對(duì)象。
- 驗(yàn)證方法認(rèn)為值對(duì)象無(wú)效,但創(chuàng)建一個(gè)新的有效對(duì)象作為替換。在這種情況下,該方法返回YES,同時(shí)保持錯(cuò)誤對(duì)象不變。在返回之前,該方法修改值引用以指向新的值對(duì)象。當(dāng)進(jìn)行修改時(shí),方法總是創(chuàng)建一個(gè)新對(duì)象,而不是修改舊對(duì)象,即使值對(duì)象是可變的。
Person* person = [[Person alloc] init];
NSError* error;
NSString* name = @"John";
if (![person validateValue:&name forKey:@"name" error:&error]) {
NSLog(@"%@",error);
}
自動(dòng)驗(yàn)證
通常,鍵值編碼協(xié)議及其默認(rèn)實(shí)現(xiàn)都不定義任何自動(dòng)執(zhí)行驗(yàn)證的機(jī)制。相反,您可以在適合您的應(yīng)用程序時(shí)使用驗(yàn)證方法。
某些其他Cocoa技術(shù)在某些情況下會(huì)自動(dòng)執(zhí)行驗(yàn)證。例如,在保存托管對(duì)象上下文時(shí),核心數(shù)據(jù)會(huì)自動(dòng)執(zhí)行驗(yàn)證。此外,在macOS中,Cocoa綁定允許您指定驗(yàn)證應(yīng)自動(dòng)進(jìn)行。
2.6 訪問(wèn)器搜索模式
NSObject提供的NSKeyValueCoding協(xié)議的默認(rèn)實(shí)現(xiàn)使用一組明確定義的規(guī)則將基于鍵的訪問(wèn)器調(diào)用映射到對(duì)象的底層屬性。這些協(xié)議方法使用一個(gè)鍵參數(shù)來(lái)搜索它們自己的對(duì)象實(shí)例,以查找訪問(wèn)器、實(shí)例變量和遵循某些命名約定的相關(guān)方法。盡管很少修改此默認(rèn)搜索,但了解它的工作原理可能會(huì)有所幫助,這既有助于跟蹤鍵值編碼對(duì)象的行為,也有助于使您自己的對(duì)象兼容。
注意:本節(jié)中的描述使用<key>或<key>作為鍵字符串的占位符,該鍵字符串在一個(gè)鍵值編碼協(xié)議方法中顯示為參數(shù),然后該方法將其用作輔助方法調(diào)用或變量名查找的一部分。映射的屬性名符合占位符的大小寫。例如,對(duì)于getter<key>和is<key>,名為hidden的屬性映射到hidden和ishiden。
基本Getter的搜索模式
給定一個(gè)鍵參數(shù)作為輸入,valueForKey:的默認(rèn)實(shí)現(xiàn)從接收valueForKey:調(diào)用的類實(shí)例中執(zhí)行以下過(guò)程。
在實(shí)例中搜索找到的第一個(gè)訪問(wèn)器方法,該方法的名稱如
get<Key>、<Key>、is<Key>或_<Key>。如果找到,則調(diào)用它并繼續(xù)執(zhí)行步驟5并返回結(jié)果。否則繼續(xù)下一步。-
如果找不到簡(jiǎn)單的訪問(wèn)器方法,則在實(shí)例中搜索名稱與模式
countOf<Key>和objectIn<Key>AtIndex:(對(duì)應(yīng)于NSArray類定義的基元方法)和<Key>AtIndex:(對(duì)應(yīng)于NSArray方法objectsAtIndexes:)匹配的方法。如果找到其中的第一個(gè)和其他兩個(gè)方法中的至少一個(gè),則創(chuàng)建一個(gè)響應(yīng)所有NSArray方法的集合代理對(duì)象并返回該對(duì)象。否則,繼續(xù)執(zhí)行步驟3。
代理對(duì)象隨后將其接收到的任何NSArray消息轉(zhuǎn)換為
countOf<Key>、objectIn<Key>AtIndex:和<Key>AtIndexes:消息的組合,并將其轉(zhuǎn)換為創(chuàng)建該對(duì)象的鍵值編碼兼容對(duì)象。如果原始對(duì)象還實(shí)現(xiàn)了一個(gè)名為get:range:的可選方法,則代理對(duì)象也會(huì)在適當(dāng)時(shí)使用該方法。實(shí)際上,代理對(duì)象與鍵值編碼兼容對(duì)象一起工作,允許底層屬性的行為如同它是NSArray,即使它不是NSArray。 -
如果找不到簡(jiǎn)單的訪問(wèn)器方法或數(shù)組訪問(wèn)方法組,則查找名為
countOf<Key>、enumeratorOf<Key>和memberOf<Key>的三個(gè)方法(對(duì)應(yīng)于NSSet類定義的基本方法)。如果找到這三個(gè)方法,則創(chuàng)建一個(gè)集合代理對(duì)象,該對(duì)象響應(yīng)所有NSSet方法并返回該對(duì)象。否則,繼續(xù)執(zhí)行步驟4。
此代理對(duì)象隨后將接收到的任何NSSet消息轉(zhuǎn)換為
countOf<Key>、enumeratorOf<Key>和memberOf<Key>:消息的組合,并將其轉(zhuǎn)換為創(chuàng)建它的對(duì)象。實(shí)際上,代理對(duì)象與鍵值編碼兼容對(duì)象一起工作,允許底層屬性的行為如同它是NSSet,即使它不是NSSet。 如果找不到簡(jiǎn)單的訪問(wèn)器方法或集合訪問(wèn)方法組,并且如果接收方的類方法
accessInstanceVariablesDirectly返回YES,則按該順序搜索名為_<key>、_is<key>、<key>或is<key>的實(shí)例變量。如果找到,直接獲取實(shí)例變量的值并繼續(xù)執(zhí)行步驟5。否則,繼續(xù)執(zhí)行步驟6。-
如果檢索到的屬性值是對(duì)象指針,則只需返回結(jié)果。
如果該值是NSNumber支持的標(biāo)量類型,請(qǐng)將其存儲(chǔ)在NSNumber實(shí)例中并返回該實(shí)例。
如果結(jié)果是NSNumber不支持的標(biāo)量類型,則轉(zhuǎn)換為NSValue對(duì)象并返回該對(duì)象。
如果所有其他操作都失敗,請(qǐng)調(diào)用
valueForUndefinedKey:。默認(rèn)情況下,這會(huì)引發(fā)異常,但NSObject的子類可能提供特定于鍵的行為。
基本Setter的搜索模式
setValue:forKey:的默認(rèn)實(shí)現(xiàn)是,給定鍵和值參數(shù)作為輸入,嘗試在接收調(diào)用的對(duì)象內(nèi)將名為key的屬性設(shè)置為value(或者,對(duì)于非對(duì)象屬性,使用以下過(guò)程將值的未包裝版本設(shè)置為value,如表示非對(duì)象值中所述):
- 按此順序查找第一個(gè)名為
set<Key>或_set<Key>的訪問(wèn)器。如果找到,使用輸入值(或者根據(jù)需要使用unwrapped值)調(diào)用它并完成。 - 如果找不到簡(jiǎn)單的訪問(wèn)器,并且類方法
accessInstanceVariablesDirectly返回YES,則按該順序查找名為_<key>、_is<key>、<key>或is<key>的實(shí)例變量。如果找到,直接用輸入值(或未包裝值)設(shè)置變量并完成。 - 在找不到訪問(wèn)器或?qū)嵗兞繒r(shí),調(diào)用
setValue:forUndefinedKey:。默認(rèn)情況下,這會(huì)引發(fā)異常,但NSObject的子類可能提供特定于鍵的行為。
可變數(shù)組的搜索模式
mutableArrayValueForKey:的默認(rèn)實(shí)現(xiàn)是,給定一個(gè)鍵參數(shù)作為輸入,使用以下過(guò)程返回接收訪問(wèn)器調(diào)用的對(duì)象內(nèi)名為key的屬性的可變代理數(shù)組:
- 查找一對(duì)名為
insertObject:in<Key>AtIndex:和removeObjectFrom<Key>AtIndex:的方法:(分別對(duì)應(yīng)于NSMutableArray原語(yǔ)方法insertObject:atIndex:和removeObjectAtIndex:),或者方法名為insert<Key>:atIndexes:和remove<Key>AtIndexes:(對(duì)應(yīng)于NSMutableArrayinsertObjects:atIndexes:和removeObjectsAtIndexes:方法)。
如果對(duì)象至少有一個(gè)插入方法和至少一個(gè)刪除方法,則返回一個(gè)代理對(duì)象,該對(duì)象通過(guò)發(fā)送insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:、insert<Key>:AtIndex:、remove<Key>AtIndex:消息的組合來(lái)響應(yīng)NSMutableArraymutableArrayValueForKey:消息。
當(dāng)接收mutableArrayValueForKey:消息的對(duì)象還實(shí)現(xiàn)了一個(gè)可選的replaceObjectIn<Key>AtIndex:withObject:或replace<Key>AtIndexes:with<Key>:,代理對(duì)象也會(huì)在適當(dāng)?shù)那闆r下使用這些方法以獲得最佳性能。
-
如果對(duì)象沒有可變數(shù)組方法,則改為查找名稱與模式集
set<Key>:匹配的訪問(wèn)器方法。在這種情況下,返回一個(gè)代理對(duì)象,該對(duì)象通過(guò)向mutableArrayValueForKey:的原始接收方發(fā)出set<Key>:消息來(lái)響應(yīng)NSMutableArray消息。注意:此步驟中描述的機(jī)制比上一步的效率低得多,因?yàn)樗赡苌婕爸貜?fù)創(chuàng)建新的集合對(duì)象,而不是修改現(xiàn)有的集合對(duì)象。因此,在設(shè)計(jì)自己的鍵值編碼兼容對(duì)象時(shí),通常應(yīng)該避免使用它。
-
如果既沒有找到可變數(shù)組方法,也沒有找到訪問(wèn)器,并且如果接收者的類直接響應(yīng)
accessInstanceVariables返回Yes,則按該順序搜索名為_<key>或<key>的實(shí)例變量。如果找到這樣的實(shí)例變量,則返回一個(gè)代理對(duì)象,該對(duì)象將接收到的每個(gè)NSMutableArray消息轉(zhuǎn)發(fā)給實(shí)例變量的值,該值通常是NSMutableArray的實(shí)例或其子類之一。
-
如果所有其他操作都失敗,則在
mutableArrayValueForKey:消息接收到NSMutableArray消息時(shí),將發(fā)出setValue:forUndefinedKey:消息的可變集合代理對(duì)象返回給mutableArrayValueForKey:消息的原始接收方。setValue:forUndefinedKey:的默認(rèn)實(shí)現(xiàn)引發(fā)了NSUndefinedKeyException,但子類可能會(huì)重寫此行為。
可變序集的搜索模式
mutableOrderedSetValueForKey:的默認(rèn)實(shí)現(xiàn):識(shí)別與valueForKey:相同的簡(jiǎn)單訪問(wèn)器方法和有序集訪問(wèn)器方法:(參見基本Getter的默認(rèn)搜索模式),并遵循相同的直接實(shí)例變量訪問(wèn)策略,但始終返回可變集合代理對(duì)象,而不是valueForKey:返回的不可變集合。此外,它還執(zhí)行以下操作:
-
查找一對(duì)名為
insertObject:in<Key>AtIndex:和removeObjectFrom<Key>AtIndex:的方法:(分別對(duì)應(yīng)于NSMutableOrderedSet原語(yǔ)方法insertObject:atIndex:和removeObjectAtIndex:),或者方法名為insert<Key>:atIndexes:和remove<Key>AtIndexes:(對(duì)應(yīng)于NSMutableOrderedSetinsertObjects:atIndexes:和removeObjectsAtIndexes:方法)。如果對(duì)象至少有一個(gè)插入方法和至少一個(gè)刪除方法,則返回一個(gè)代理對(duì)象,該對(duì)象通過(guò)發(fā)送
insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:、insert<Key>:AtIndex:、remove<Key>AtIndex:消息的組合來(lái)響應(yīng)NSMutableOrderedSetmutableOrderedSetValueForKey:消息。當(dāng)接收
mutableOrderedSetValueForKey:消息的對(duì)象還實(shí)現(xiàn)了一個(gè)可選的replaceObjectIn<Key>AtIndex:withObject:或replace<Key>AtIndexes:with<Key>:,代理對(duì)象也會(huì)在適當(dāng)?shù)那闆r下使用這些方法以獲得最佳性能。 -
如果對(duì)象沒有可變數(shù)組方法,則改為查找名稱與模式集
set<Key>:匹配的訪問(wèn)器方法。在這種情況下,返回一個(gè)代理對(duì)象,該對(duì)象通過(guò)向mutableOrderedSetValueForKey:的原始接收方發(fā)出set<Key>:消息來(lái)響應(yīng)NSMutableOrderedSet消息。注意:此步驟中描述的機(jī)制比上一步的效率低得多,因?yàn)樗赡苌婕爸貜?fù)創(chuàng)建新的集合對(duì)象,而不是修改現(xiàn)有的集合對(duì)象。因此,在設(shè)計(jì)自己的鍵值編碼兼容對(duì)象時(shí),通常應(yīng)該避免使用它。
-
如果既沒有找到可變數(shù)組方法,也沒有找到訪問(wèn)器,并且如果接收者的類直接響應(yīng)
accessInstanceVariables返回Yes,則按該順序搜索名為_<key>或<key>的實(shí)例變量。如果找到這樣的實(shí)例變量,則返回一個(gè)代理對(duì)象,該對(duì)象將接收到的每個(gè)NSMutableOrderedSet消息轉(zhuǎn)發(fā)給實(shí)例變量的值,該值通常是NSMutableOrderedSet的實(shí)例或其子類之一。
-
如果所有其他操作都失敗,則在
mutableOrderedSetValueForKey:消息接收到NSMutableOrderedSet消息時(shí),將發(fā)出setValue:forUndefinedKey:消息的可變集合代理對(duì)象返回給mutableOrderedSetValueForKey:消息的原始接收方。setValue:forUndefinedKey:的默認(rèn)實(shí)現(xiàn)引發(fā)了NSUndefinedKeyException,但子類可能會(huì)重寫此行為。
可變集的搜索模式
mutableSetValueForKey:的默認(rèn)實(shí)現(xiàn)是,給定一個(gè)鍵參數(shù)作為輸入,使用以下過(guò)程返回接收訪問(wèn)器調(diào)用的對(duì)象內(nèi)名為key的數(shù)組屬性的可變代理集:
-
搜索名為
add<Key>Object:和remove<Key>Object:(分別對(duì)應(yīng)于NSMutableSetaddObject:和removeObject:)的方法,以及add<Key>:和remove<Key>:(對(duì)應(yīng)于NSMutableSetunionSet:和minusSet:)。如果至少找到一個(gè)添加方法和至少一個(gè)刪除方法,則返回一個(gè)代理對(duì)象,該代理對(duì)象將向
mutableSetValueForKey:的原始接收者發(fā)送add<Key>Object:、remove<Key>Object:、add<Key>:和remove:消息的組合,用于接收的每個(gè)NSMutableSet消息。代理對(duì)象還使用
intersect<Key>:或set<Key>:等名稱的方法以獲得最佳性能(如果它們可用)。 如果
mutableSetValueForKey:調(diào)用的接收者是托管對(duì)象,則搜索模式不會(huì)像對(duì)非托管對(duì)象那樣繼續(xù)。有關(guān)詳細(xì)信息,請(qǐng)參閱Core Data Programming Guide中的托管對(duì)象訪問(wèn)器方法。-
如果找不到可變集方法,并且該對(duì)象不是托管對(duì)象,則搜索名稱類似
set<Key>:的訪問(wèn)器方法。如果找到這樣的方法,則返回的代理對(duì)象將為其接收的每個(gè)NSMutableSet消息向mutableSetValueForKey:的原始接收方發(fā)送set<Key>:消息。注意:此步驟中描述的機(jī)制比第一步效率低得多,因?yàn)樗赡苌婕爸貜?fù)創(chuàng)建新的集合對(duì)象,而不是修改現(xiàn)有的集合對(duì)象。因此,在設(shè)計(jì)自己的鍵值編碼兼容對(duì)象時(shí),通常應(yīng)該避免使用它。
如果找不到可變集方法和訪問(wèn)器方法,并且如果
accessInstanceVariablesDirectly類方法返回YES,則按該順序搜索名稱類似于_<key>或<key>的實(shí)例變量。如果找到這樣的實(shí)例變量,則代理對(duì)象將收到的每個(gè)NSMutableSet消息轉(zhuǎn)發(fā)給實(shí)例變量的值,該值通常是NSMutableSet的實(shí)例或其子類之一。如果所有其他操作都失敗,則返回的代理對(duì)象將通過(guò)向
mutableSetValueForKey:的原始接收方發(fā)送setValue:forUndefinedKey:消息來(lái)響應(yīng)它接收到的任何NSMutableSet消息。
3. KVC的應(yīng)用
3.1 實(shí)現(xiàn)基本的鍵值編碼
當(dāng)為對(duì)象采用鍵值編碼時(shí),您依賴于NSKeyValueCoding協(xié)議的默認(rèn)實(shí)現(xiàn),方法是讓您的對(duì)象繼承自NSObject或其許多子類之一。而默認(rèn)實(shí)現(xiàn)則依賴于您按照某些定義良好的模式定義對(duì)象的實(shí)例變量(或ivar)和訪問(wèn)器方法,以便它在接收到鍵值編碼的消息(如valueForKey:和setValue:forKey:)時(shí)可以將鍵字符串與屬性相關(guān)聯(lián)。
您通常遵循Objective-C中的標(biāo)準(zhǔn)模式,只需使用@property語(yǔ)句聲明一個(gè)屬性,并允許編譯器自動(dòng)合成ivar和訪問(wèn)器。默認(rèn)情況下,編譯器遵循預(yù)期的模式。
注意:在Swift中,只需以通常的方式聲明一個(gè)屬性,就會(huì)自動(dòng)生成適當(dāng)?shù)脑L問(wèn)器,而且您永遠(yuǎn)不會(huì)直接與ivar交互。有關(guān)Swift中屬性的更多信息,請(qǐng)閱讀Swift編程語(yǔ)言(Swift 3)中的屬性。有關(guān)從Swift與Objective-C屬性交互的特定信息,請(qǐng)閱讀使用Swift與Cocoa和Objective-C(Swift 3)中的Accessing properties。
如果您確實(shí)需要在Objective-C中手動(dòng)實(shí)現(xiàn)訪問(wèn)器或ivar,請(qǐng)遵循本節(jié)中的指南以保持基本的遵從性。
要提供增強(qiáng)與任何語(yǔ)言中的對(duì)象集合屬性交互的附加功能,請(qǐng)實(shí)現(xiàn)定義集合方法中描述的方法。
要使用鍵值驗(yàn)證進(jìn)一步增強(qiáng)對(duì)象,請(qǐng)實(shí)現(xiàn)添加驗(yàn)證中描述的方法。
注意:鍵值編碼的默認(rèn)實(shí)現(xiàn)使用比這里描述的更廣泛的ivar和訪問(wèn)器。如果有使用其他變量或訪問(wèn)器約定的遺留代碼,請(qǐng)檢查訪問(wèn)器搜索模式中的搜索模式,以確定默認(rèn)實(shí)現(xiàn)是否可以找到對(duì)象的屬性。
基本的Getter
要實(shí)現(xiàn)返回屬性值的getter,同時(shí)可能還要執(zhí)行其他自定義工作,請(qǐng)使用與該屬性類似的方法,例如對(duì)于title字符串屬性:
- (NSString*)title
{
// Extra getter logic…
return _title;
}
對(duì)于包含布爾值的屬性,也可以使用前綴為is的方法,例如對(duì)于隱藏的布爾屬性:
- (BOOL)isHidden
{
// Extra getter logic…
return _hidden;
}
當(dāng)屬性是標(biāo)量或結(jié)構(gòu)時(shí),鍵值編碼的默認(rèn)實(shí)現(xiàn)將值包裝在對(duì)象中,以便在協(xié)議方法的接口上使用。你不需要做任何特別的事情來(lái)支持這種行為。
基本的Setter
要實(shí)現(xiàn)存儲(chǔ)屬性值的setter,請(qǐng)使用以單詞set為前綴的屬性大寫名稱的方法。對(duì)于隱藏屬性:
- (void)setHidden:(BOOL)hidden
{
// Extra setter logic…
_hidden = hidden;
}
警告:從不從set<Key>:方法內(nèi)部調(diào)用Validating Properties中描述的驗(yàn)證方法。
當(dāng)一個(gè)屬性是非對(duì)象類型(如布爾值hidden)時(shí),協(xié)議的默認(rèn)實(shí)現(xiàn)會(huì)檢測(cè)底層數(shù)據(jù)類型,并在將其應(yīng)用于setter之前取消對(duì)來(lái)自setValue:forKey:的對(duì)象值(本例中是NSNumber實(shí)例)的包裝。你不需要在setter中處理這個(gè)問(wèn)題。但是,如果有可能將nil值寫入非對(duì)象屬性,則可以重寫setNilValueForKey:來(lái)處理這種情況,如處理非對(duì)象值中所述。hidden屬性的適當(dāng)行為可能只是將nil解釋為NO:
- (void)setNilValueForKey:(NSString *)key
{
if ([key isEqualToString:@"hidden"]) {
[self setValue:@(NO) forKey:@"hidden"];
} else {
[super setNilValueForKey:key];
}
}
如果合適,即使允許編譯器合成setter,也可以提供上述方法重寫。
實(shí)例變量
當(dāng)一個(gè)鍵值編碼訪問(wèn)器方法的默認(rèn)實(shí)現(xiàn)找不到屬性的訪問(wèn)器時(shí),它會(huì)直接查詢其類的accessInstanceVariablesDirectly方法,以查看該類是否允許直接使用實(shí)例變量。默認(rèn)情況下,這個(gè)類方法返回YES,盡管您可以重寫這個(gè)方法以返回NO。
如果您確實(shí)允許使用ivar,請(qǐng)確保它們以通常的方式命名,使用以下劃線_為前綴的屬性名。通常,編譯器在自動(dòng)合成屬性時(shí)會(huì)為您執(zhí)行此操作,但如果您使用顯式的@synthey指令,則可以自己強(qiáng)制執(zhí)行此命名:
@synthesize title = _title;
在某些情況下,不使用@synthey指令或允許編譯器自動(dòng)合成屬性,而是使用@dynamic指令通知編譯器您將在運(yùn)行時(shí)提供getter和setter。這樣做可能是為了避免自動(dòng)合成getter,以便可以提供集合訪問(wèn)器,如定義集合方法中所述。在這種情況下,您可以自己聲明ivar作為接口聲明的一部分:
@interface MyObject : NSObject {
NSString* _title;
}
@property (nonatomic) NSString* title;
@end
3.2 定義集合方法
當(dāng)使用標(biāo)準(zhǔn)命名約定創(chuàng)建訪問(wèn)器和ivar時(shí),如實(shí)現(xiàn)基本鍵值編碼遵從性中所述,鍵值編碼協(xié)議的默認(rèn)實(shí)現(xiàn)可以根據(jù)鍵值編碼的消息來(lái)定位它們。對(duì)于表示許多關(guān)系的集合對(duì)象,這與對(duì)于其他屬性一樣正確。但是,如果實(shí)現(xiàn)集合訪問(wèn)器方法而不是集合屬性的基本訪問(wèn)器,或者除此之外,還可以:
與NSArray或NSSet以外的類建立多個(gè)關(guān)系模型。在對(duì)象中實(shí)現(xiàn)集合方法時(shí),鍵值getter的默認(rèn)實(shí)現(xiàn)返回一個(gè)代理對(duì)象,該對(duì)象調(diào)用這些方法以響應(yīng)其接收到的后續(xù)NSArray或NSSet消息?;A(chǔ)屬性對(duì)象不必是NSArray或NSSet本身,因?yàn)榇韺?duì)象使用集合方法提供預(yù)期的行為。
在對(duì)多對(duì)多關(guān)系的內(nèi)容進(jìn)行變異時(shí)獲得更高的性能。該協(xié)議的默認(rèn)實(shí)現(xiàn)使用您的集合方法對(duì)基礎(chǔ)屬性進(jìn)行適當(dāng)?shù)淖儺悾皇鞘褂没緎etter重復(fù)創(chuàng)建新的集合對(duì)象以響應(yīng)每次更改。
提供對(duì)對(duì)象集合屬性內(nèi)容的符合性訪問(wèn)的鍵值。有關(guān)鍵值觀測(cè)的更多信息,請(qǐng)閱讀《鍵值觀測(cè)編程指南》。
您可以實(shí)現(xiàn)兩種類型的集合訪問(wèn)器之一,具體取決于您是希望關(guān)系表現(xiàn)為索引的有序集合(如NSArray對(duì)象)還是無(wú)序的唯一集合(如NSSet對(duì)象)。在這兩種情況下,您至少實(shí)現(xiàn)一組方法來(lái)支持對(duì)屬性的讀取訪問(wèn),然后添加另一組方法來(lái)啟用集合內(nèi)容的變異。
注意:鍵值編碼協(xié)議不聲明本節(jié)中描述的方法。相反,NSObject提供的協(xié)議的默認(rèn)實(shí)現(xiàn)會(huì)在與鍵值編碼兼容的對(duì)象中查找這些方法,如訪問(wèn)器搜索模式中所述,并使用它們來(lái)處理作為協(xié)議一部分的鍵值編碼消息。
訪問(wèn)索引集合
添加索引訪問(wèn)器方法以提供一種機(jī)制,用于計(jì)算、檢索、添加和替換有序關(guān)系中的對(duì)象。底層對(duì)象通常是NSArray或NSMutableArray的實(shí)例,但如果提供集合訪問(wèn)器,則可以像操作數(shù)組一樣操作實(shí)現(xiàn)這些方法的任何對(duì)象屬性。
索引集合Getter
對(duì)于沒有默認(rèn)getter的集合屬性,如果提供以下索引集合getter方法,則協(xié)議的默認(rèn)實(shí)現(xiàn)將響應(yīng)valueForKey:消息返回一個(gè)行為類似于NSArray的代理對(duì)象,但調(diào)用以下集合方法來(lái)完成其工作。
注意:在現(xiàn)代Objective-C中,編譯器在默認(rèn)情況下為每個(gè)屬性合成getter,因此默認(rèn)實(shí)現(xiàn)不會(huì)創(chuàng)建使用本節(jié)中方法的只讀代理(注意基本getter的搜索模式中的訪問(wèn)器搜索順序)。您可以通過(guò)不聲明屬性(僅依賴于ivar)或?qū)傩月暶鳛锧dynamic(表示您計(jì)劃在運(yùn)行時(shí)提供訪問(wèn)器行為)來(lái)解決此問(wèn)題。不管怎樣,編譯器都不會(huì)提供默認(rèn)getter,默認(rèn)實(shí)現(xiàn)使用以下方法。
-
countOf<Key>
此方法將“多對(duì)多”關(guān)系中的對(duì)象數(shù)作為NSUInteger返回,就像NSArray原語(yǔ)方法count一樣。實(shí)際上,當(dāng)基礎(chǔ)屬性是NSArray時(shí),可以使用該方法提供結(jié)果。
例如,對(duì)于表示銀行交易列表并由稱為交易的NSArray支持的多對(duì)多關(guān)系:
- (NSUInteger)countOfTransactions { return [self.transactions count]; } -
objectIn<key>AtIndex: 或 <key>AtIndexes:
第一個(gè)返回對(duì)多關(guān)系中指定索引處的對(duì)象,而第二個(gè)返回NSIndexSet參數(shù)指定索引處的對(duì)象數(shù)組。這些分別對(duì)應(yīng)于NSArray方法
objectAtIndex:和objectsAtIndexes:。你只需要實(shí)現(xiàn)其中一個(gè)。- (id)objectInTransactionsAtIndex:(NSUInteger)index { return [self.transactions objectAtIndex:index]; } - (NSArray *)transactionsAtIndexes:(NSIndexSet *)indexes { return [self.transactions objectsAtIndexes:indexes]; } -
get<Key>:range:
此方法是可選的,但可以提高性能。它返回集合中屬于指定范圍的對(duì)象,并對(duì)應(yīng)于NSArray方法
getObjects:range:。- (void)getTransactions:(Transaction * __unsafe_unretained *)buffer range:(NSRange)inRange { [self.transactions getObjects:buffer range:inRange]; }
索引集合變異器
支持與索引訪問(wèn)器的可變多對(duì)多關(guān)系需要實(shí)現(xiàn)一組不同的方法。當(dāng)您提供這些setter方法時(shí),響應(yīng)mutableArrayValueForKey:消息的默認(rèn)實(shí)現(xiàn)將返回一個(gè)代理對(duì)象,該代理對(duì)象的行為類似于NSMutableArray對(duì)象,但使用對(duì)象的方法來(lái)完成其工作。這通常比直接返回NSMutableArray對(duì)象更有效。它還使多對(duì)多關(guān)系的內(nèi)容與鍵值觀測(cè)兼容(請(qǐng)參閱鍵值觀測(cè)編程指南)。
為了使對(duì)象鍵值編碼與可變的多對(duì)多關(guān)系兼容,請(qǐng)實(shí)現(xiàn)以下方法:
-
insertObject:in<Key>AtIndex: 或 insert<Key>:atIndexes:
第一個(gè)接收要插入的對(duì)象和指定要插入的索引的NSUInteger。第二個(gè)將對(duì)象數(shù)組插入到集合中由傳遞的NSIndexSet指定的索引處。這些方法類似于NSMutableArray方法
insertObject:atIndex:和insertObjects:atIndex:。只需要這些方法之一。- (void)insertObject:(Transaction *)transaction inTransactionsAtIndex:(NSUInteger)index { [self.transactions insertObject:transaction atIndex:index]; } - (void)insertTransactions:(NSArray *)transactionArray atIndexes:(NSIndexSet *)indexes { [self.transactions insertObjects:transactionArray atIndexes:indexes]; } -
removeObjectFrom<Key>AtIndex: 或 remove<Key>AtIndexes:
第一個(gè)接收指定要從關(guān)系中移除的對(duì)象的索引的NSUInteger值。第二個(gè)接收NSIndexSet對(duì)象,指定要從關(guān)系中刪除的對(duì)象的索引。這些方法分別對(duì)應(yīng)于NSMutableArray方法
removeObjectAtIndex:和removeObjectsAtIndexes:。只需要這些方法之一。- (void)removeObjectFromTransactionsAtIndex:(NSUInteger)index { [self.transactions removeObjectAtIndex:index]; } - (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes { [self.transactions removeObjectsAtIndexes:indexes]; } -
replaceObjectIn<Key>AtIndex:withObject: 或 replace<Key>AtIndexes:with<Key>:
這些替換訪問(wèn)器為代理對(duì)象提供了一種直接替換集合中對(duì)象的方法,而無(wú)需依次移除一個(gè)對(duì)象并插入另一個(gè)對(duì)象。它們對(duì)應(yīng)于NSMutableArray方法
replaceObjectAtIndex:withObject:和replaceObjectsAtIndexes:withObjects:。當(dāng)分析應(yīng)用程序時(shí)發(fā)現(xiàn)性能問(wèn)題時(shí),您可以選擇提供這些方法。- (void)replaceObjectInTransactionsAtIndex:(NSUInteger)index withObject:(id)anObject { [self.transactions replaceObjectAtIndex:index withObject:anObject]; } - (void)replaceTransactionsAtIndexes:(NSIndexSet *)indexes withTransactions:(NSArray *)transactionArray { [self.transactions replaceObjectsAtIndexes:indexes withObjects:transactionArray]; }
訪問(wèn)無(wú)序集合
當(dāng)您提供以下集合getter方法以返回集合中的對(duì)象數(shù)、遍歷集合對(duì)象并測(cè)試集合中是否已存在對(duì)象時(shí),協(xié)議的默認(rèn)實(shí)現(xiàn)將響應(yīng)valueForKey:消息返回一個(gè)行為類似NSSet的代理對(duì)象,但調(diào)用以下收集方法來(lái)完成其工作。
注意:在現(xiàn)代Objective-C中,編譯器在默認(rèn)情況下為每個(gè)屬性合成getter,因此默認(rèn)實(shí)現(xiàn)不會(huì)創(chuàng)建使用本節(jié)中方法的只讀代理(注意基本getter的搜索模式中的訪問(wèn)器搜索順序)。您可以通過(guò)不聲明屬性(僅依賴于ivar)或?qū)傩月暶鳛锧dynamic(表示您計(jì)劃在運(yùn)行時(shí)提供訪問(wèn)器行為)來(lái)解決此問(wèn)題。不管怎樣,編譯器都不會(huì)提供默認(rèn)getter,默認(rèn)實(shí)現(xiàn)使用以下方法。
無(wú)序集合Getter
-
countOf<Key>
此必需方法返回關(guān)系中與NSSet方法計(jì)數(shù)相對(duì)應(yīng)的項(xiàng)數(shù)。當(dāng)?shù)讓訉?duì)象是NSSet時(shí),可以直接調(diào)用此方法。例如,對(duì)于名為employees的NSSet對(duì)象,包含Employee對(duì)象:
- (NSUInteger)countOfEmployees { return [self.employees count]; } -
enumeratorOf<Key>
此必需的方法返回一個(gè)用于對(duì)關(guān)系中的項(xiàng)進(jìn)行迭代的NSEnumerator實(shí)例。該方法對(duì)應(yīng)于NSSet方法
objectEnumerator。- (NSEnumerator *)enumeratorOfEmployees { return [self.employees objectEnumerator]; } -
memberOf<Key>:
此方法將作為參數(shù)傳遞的對(duì)象與集合的內(nèi)容進(jìn)行比較,并返回匹配的對(duì)象作為結(jié)果,如果找不到匹配的對(duì)象,則返回nil。如果手動(dòng)實(shí)現(xiàn)比較,則通常使用
isEqual:比較對(duì)象。當(dāng)基礎(chǔ)對(duì)象是NSSet對(duì)象時(shí),可以使用等效的member:方法:- (Employee *)memberOfEmployees:(Employee *)anObject { return [self.employees member:anObject]; }
無(wú)序集合變異器
支持與無(wú)序訪問(wèn)器的可變多對(duì)多關(guān)系需要實(shí)現(xiàn)其他方法。實(shí)現(xiàn)可變無(wú)序訪問(wèn)器以允許對(duì)象提供無(wú)序的集代理對(duì)象以響應(yīng)mutableSetValueForKey:方法。實(shí)現(xiàn)這些訪問(wèn)器比依賴直接返回可變對(duì)象以更改關(guān)系中的數(shù)據(jù)的訪問(wèn)器要高效得多。它還使您的類鍵值觀測(cè)與收集的對(duì)象兼容(請(qǐng)參閱鍵值觀測(cè)編程指南)。
為了對(duì)可變無(wú)序到多個(gè)關(guān)系進(jìn)行鍵值編碼,請(qǐng)執(zhí)行以下方法:
-
add<Key>Object: 或 add<Key>:
這些方法將單個(gè)項(xiàng)或一組項(xiàng)添加到關(guān)系中。向關(guān)系中添加一組項(xiàng)時(shí),請(qǐng)確保關(guān)系中不存在等效對(duì)象。它們類似于NSMutableSet方法
addObject:和unionSet:。只需要這些方法之一。- (void)addEmployeesObject:(Employee *)anObject { [self.employees addObject:anObject]; } - (void)addEmployees:(NSSet *)manyObjects { [self.employees unionSet:manyObjects]; } -
remove<Key>Object: 或 remove<Key>:
這些方法從關(guān)系中移除單個(gè)項(xiàng)或一組項(xiàng)。它們類似于NSMutableSet方法
removeObject:和minusSet:。只需要這些方法之一。- (void)removeEmployeesObject:(Employee *)anObject { [self.employees removeObject:anObject]; } - (void)removeEmployees:(NSSet *)manyObjects { [self.employees minusSet:manyObjects]; } -
intersect<Key>:
此方法接收一個(gè)NSSet參數(shù),從關(guān)系中刪除輸入集和集合集都不通用的所有對(duì)象。這相當(dāng)于NSMutableSet方法
intersectSet:。當(dāng)分析指示圍繞集合內(nèi)容更新的性能問(wèn)題時(shí),可以選擇實(shí)現(xiàn)此方法。- (void)intersectEmployees:(NSSet *)otherObjects { return [self.employees intersectSet:otherObjects]; }
3.3 處理非對(duì)象值
通常,與鍵值編碼兼容的對(duì)象依賴于鍵值編碼的默認(rèn)實(shí)現(xiàn)來(lái)自動(dòng)包裝和展開非對(duì)象屬性。但是,可以覆蓋默認(rèn)行為。這樣做的最常見原因是處理在非對(duì)象屬性上存儲(chǔ)nil值的嘗試。
注意:因?yàn)镾wift中的所有屬性都是對(duì)象,所以本節(jié)僅適用于Objective-C屬性。
如果與鍵值編碼兼容的對(duì)象接收到一條setValue:forKey:消息,其中nil作為非對(duì)象屬性的值傳遞,則默認(rèn)實(shí)現(xiàn)沒有適當(dāng)?shù)耐ㄓ貌僮鬟^(guò)程。因此,它會(huì)給自己發(fā)送一條setNilValueForKey:消息,您可以覆蓋它。
setNilValueForKey:的默認(rèn)實(shí)現(xiàn):引發(fā)NSInvalidArgumentException異常,但您可以提供適當(dāng)?shù)奶囟ㄓ趯?shí)現(xiàn)的行為。
例如,下面的代碼通過(guò)將age設(shè)置為0來(lái)響應(yīng)將一個(gè)人的年齡設(shè)置為nil值的嘗試。
- (void)setNilValueForKey:(NSString *)key
{
if ([key isEqualToString:@"age"]) {
[self setValue:@(0) forKey:@”age”];
} else {
[super setNilValueForKey:key];
}
}
注意:為了向后兼容,當(dāng)對(duì)象重寫了deprecated的unableToSetNilForKey:方法時(shí),setValue:forKey:調(diào)用該方法而不是setNilValueForKey:。
3.4添加驗(yàn)證
鍵值編碼協(xié)議定義了通過(guò)鍵或鍵路徑驗(yàn)證屬性的方法。這些方法的默認(rèn)實(shí)現(xiàn)反過(guò)來(lái)依賴于您按照與訪問(wèn)器方法相似的命名模式定義方法。具體地說(shuō),可以為任何具有要驗(yàn)證的名稱鍵的屬性提供validate<Key>:error:方法。默認(rèn)實(shí)現(xiàn)搜索此項(xiàng)以響應(yīng)鍵編碼的validateValue:forKey:error:消息。
如果不為某個(gè)屬性提供驗(yàn)證方法,則協(xié)議的默認(rèn)實(shí)現(xiàn)假定該屬性的驗(yàn)證成功,而不考慮該值。這意味著您可以選擇逐個(gè)屬性進(jìn)行驗(yàn)證。
注意:您通常只使用Objective-C中描述的驗(yàn)證。在Swift中,屬性驗(yàn)證更習(xí)慣于通過(guò)依賴編譯器對(duì)選項(xiàng)和強(qiáng)類型檢查的支持來(lái)處理,而使用內(nèi)置的willSet和didSet屬性觀察程序來(lái)測(cè)試任何運(yùn)行時(shí)API協(xié)定,如Swift編程語(yǔ)言(Swift 3)。
實(shí)現(xiàn)驗(yàn)證方法
當(dāng)為屬性提供驗(yàn)證方法時(shí),該方法通過(guò)引用接收兩個(gè)參數(shù):要驗(yàn)證的值對(duì)象和用于返回錯(cuò)誤信息的NSError。因此,驗(yàn)證方法可以采取以下三種操作之一:
值對(duì)象有效時(shí),返回YES而不更改值對(duì)象或錯(cuò)誤。
-
當(dāng)值對(duì)象無(wú)效,并且您不能或不想提供有效的替代項(xiàng)時(shí),請(qǐng)將error參數(shù)設(shè)置為一個(gè)NSError對(duì)象,該對(duì)象指示失敗的原因并返回NO。
注意:在嘗試設(shè)置錯(cuò)誤引用之前,請(qǐng)始終測(cè)試該引用是否為空。
當(dāng)值對(duì)象無(wú)效,但您知道有效的替代項(xiàng)時(shí),創(chuàng)建有效對(duì)象,將值引用分配給新對(duì)象,并在不修改錯(cuò)誤引用的情況下返回YES。如果提供另一個(gè)值,則始終返回一個(gè)新對(duì)象,而不是修改正在驗(yàn)證的對(duì)象,即使原始對(duì)象是可變的。
下面的代碼演示了name字符串屬性的驗(yàn)證方法,該方法確保value對(duì)象不是nil,并且名稱是最小長(zhǎng)度。如果驗(yàn)證失敗,則此方法不替換其他值。
- (BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError{
if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 2)) {
if (outError != NULL) {
*outError = [NSError errorWithDomain:PersonErrorDomain
code:PersonInvalidNameCode
userInfo:@{ NSLocalizedDescriptionKey
: @"Name too short" }];
}
return NO;
}
return YES;
}
標(biāo)量值的驗(yàn)證
驗(yàn)證方法接收的參數(shù)是一個(gè)對(duì)象。因此,非對(duì)象屬性的值包裝在NSValue或NSNumber對(duì)象中,如在表示非對(duì)象值中所述。
下面的代碼演示了標(biāo)量屬性age的驗(yàn)證方法。在這種情況下,通過(guò)創(chuàng)建設(shè)置為零的有效值并返回YES來(lái)處理一個(gè)潛在的無(wú)效條件,即nil值。您還可以在setNilValueForKey:覆蓋中處理此特定條件,因?yàn)轭惖挠脩艨赡懿粫?huì)調(diào)用驗(yàn)證方法。
- (BOOL)validateAge:(id *)ioValue error:(NSError * __autoreleasing *)outError {
if (*ioValue == nil) {
// Value is nil: Might also handle in setNilValueForKey
*ioValue = @(0);
} else if ([*ioValue floatValue] < 0.0) {
if (outError != NULL) {
*outError = [NSError errorWithDomain:PersonErrorDomain
code:PersonInvalidAgeCode
userInfo:@{ NSLocalizedDescriptionKey
: @"Age cannot be negative" }];
}
return NO;
}
return YES;
}
3.5 描述屬性關(guān)系
類描述提供了一種描述類中對(duì)單和對(duì)多屬性的方法。定義類屬性之間的這些關(guān)系允許使用鍵值編碼對(duì)這些屬性進(jìn)行更智能、更靈活的操作。
類描述
NSClassDescription是一個(gè)基類,它提供獲取類的元數(shù)據(jù)的接口。類描述對(duì)象記錄特定類的對(duì)象的可用屬性以及該類的對(duì)象與其他對(duì)象之間的關(guān)系(一對(duì)一、一對(duì)多和多對(duì)一)。例如,attributeKeys方法返回為類定義的所有屬性的列表;toManyRelationshipKeys和toOneRelationshipKeys方法返回定義為多對(duì)一關(guān)系的鍵數(shù)組;和inverseRelationshipKey:返回從所提供鍵的關(guān)系的目標(biāo)指向接收方的關(guān)系的名稱。
NSClassDescription沒有定義定義關(guān)系的方法。具體的子類必須定義這些方法。一旦創(chuàng)建,就可以使用NSClassDescriptionregisterClassDescription:forClass:類方法注冊(cè)類描述。
NSScriptClassDescription是Cocoa中提供的NSClassDescription的唯一具體子類。它封裝了應(yīng)用程序的腳本信息。
3.6 性能設(shè)計(jì)
鍵值編碼是有效的,特別是當(dāng)您依賴于默認(rèn)實(shí)現(xiàn)來(lái)完成大部分工作時(shí),但是它確實(shí)添加了一個(gè)間接級(jí)別,比直接方法調(diào)用稍微慢一些。只有當(dāng)您可以從它提供的靈活性中獲益,或者允許您的對(duì)象參與依賴于鍵值編碼的Cocoa技術(shù)時(shí),才使用鍵值編碼。
重寫鍵值編碼方法
通常,通過(guò)確保對(duì)象繼承自NSObject,然后提供本教程描述的特定于屬性的訪問(wèn)器和相關(guān)方法,可以使對(duì)象的鍵值編碼兼容。很少需要重寫鍵值編碼訪問(wèn)器的默認(rèn)實(shí)現(xiàn),如valueForKey:和setValue:forKey:,或者基于鍵的驗(yàn)證方法,如validateValue:forKey:。因?yàn)檫@些實(shí)現(xiàn)緩存有關(guān)運(yùn)行時(shí)環(huán)境的信息以提高效率,所以如果確實(shí)重寫它們以引入自定義邏輯,請(qǐng)確保在返回之前調(diào)用超類中的默認(rèn)實(shí)現(xiàn)。
優(yōu)化對(duì)多個(gè)關(guān)系
當(dāng)實(shí)現(xiàn)對(duì)多個(gè)關(guān)系時(shí),訪問(wèn)器的索引形式在許多情況下都會(huì)提供顯著的性能提升,特別是對(duì)于可變集合。
3.7 合規(guī)檢查表
遵循本節(jié)總結(jié)的步驟,以確保您的對(duì)象符合鍵值編碼。有關(guān)詳細(xì)信息,請(qǐng)參見前幾節(jié)。
屬性和一種關(guān)系遵從性
對(duì)于作為屬性或?qū)σ魂P(guān)系的每個(gè)屬性:
-
實(shí)現(xiàn)一個(gè)名為
<key>或is<key>的方法,或者創(chuàng)建一個(gè)實(shí)例變量<key>或_<key>。編譯器通常在自動(dòng)合成屬性時(shí)為您執(zhí)行此操作。注意:盡管屬性名稱通常以小寫字母開頭,但協(xié)議的默認(rèn)實(shí)現(xiàn)也適用于以大寫字母開頭的名稱,例如URL。
-
如果屬性是可變的,則實(shí)現(xiàn)set<Key>:方法。編譯器通常在允許它自動(dòng)合成屬性時(shí)為您執(zhí)行此操作。
重要:如果重寫默認(rèn)setter,請(qǐng)確保不要調(diào)用協(xié)議的任何驗(yàn)證方法。
如果該屬性是標(biāo)量,則重寫
setNilValueForKey:方法,以優(yōu)雅地處理將nil值分配給標(biāo)量屬性的情況。
索引對(duì)多個(gè)關(guān)系遵從性
對(duì)于每個(gè)有序的多個(gè)關(guān)系(例如NSArray對(duì)象)的屬性:
實(shí)現(xiàn)一個(gè)名為
<key>的方法,該方法返回一個(gè)數(shù)組,或者有一個(gè)名為<key>或_<key>的數(shù)組實(shí)例變量。編譯器通常在自動(dòng)合成屬性時(shí)為您執(zhí)行此操作。或者,實(shí)現(xiàn)方法
countOf<Key>和objectIn<Key>AtIndex:和<Key>AtIndex:中的一個(gè)或兩個(gè)。(可選)實(shí)現(xiàn)
get<Key>:range:以提高性能。
此外,如果屬性是可變的:
實(shí)現(xiàn)
insertObject:in<Key>AtIndex:和insert<Key>:AtIndex:中的一個(gè)或兩個(gè)方法。實(shí)現(xiàn)
removeObjectFrom<Key>AtIndex:和remove<Key>AtIndex:方法中的一個(gè)或兩個(gè)。可選地,實(shí)現(xiàn)
replaceObjectIn<Key>AtIndex:withObject:或replace<Key>AtIndex:with<Key>:以提高性能。
無(wú)序?qū)Χ嚓P(guān)系遵從性
對(duì)于無(wú)序的多對(duì)多關(guān)系(如NSSet對(duì)象)的每個(gè)屬性:
實(shí)現(xiàn)返回集合的
<key>,或具有名為<key>或_<key>的NSSet實(shí)例變量。編譯器通常在自動(dòng)合成屬性時(shí)為您執(zhí)行此操作。或者,實(shí)現(xiàn)方法
countOf<Key>、enumeratorOf<Key>和memberOf<Key>:。
此外,如果屬性是可變的:
實(shí)現(xiàn)一個(gè)或兩個(gè)方法
add<Key>Object:和add<Key>:。實(shí)現(xiàn)一個(gè)或兩個(gè)方法
remove<Key>Object:和remove<Key>:。可選地,實(shí)現(xiàn)
intersect<Key>:以提高性能。
驗(yàn)證
選擇對(duì)需要驗(yàn)證的屬性進(jìn)行驗(yàn)證:
- 實(shí)現(xiàn)
validate<Key>:error:方法,返回指示值有效性的布爾值,以及適當(dāng)時(shí)返回錯(cuò)誤對(duì)象的引用。