KVC官方文檔

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í)別出ownercurrentBalancetransactions,這是它的屬性名。您不必調(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ò)程。

  1. 在實(shí)例中搜索找到的第一個(gè)訪問(wèn)器方法,該方法的名稱如get<Key>、<Key>is<Key>_<Key>。如果找到,則調(diào)用它并繼續(xù)執(zhí)行步驟5并返回結(jié)果。否則繼續(xù)下一步。

  2. 如果找不到簡(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。

  3. 如果找不到簡(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。

  4. 如果找不到簡(jiǎn)單的訪問(wèn)器方法或集合訪問(wèn)方法組,并且如果接收方的類方法accessInstanceVariablesDirectly返回YES,則按該順序搜索名為_<key>、_is<key>、<key>is<key>的實(shí)例變量。如果找到,直接獲取實(shí)例變量的值并繼續(xù)執(zhí)行步驟5。否則,繼續(xù)執(zhí)行步驟6。

  5. 如果檢索到的屬性值是對(duì)象指針,則只需返回結(jié)果。

    如果該值是NSNumber支持的標(biāo)量類型,請(qǐng)將其存儲(chǔ)在NSNumber實(shí)例中并返回該實(shí)例。

    如果結(jié)果是NSNumber不支持的標(biāo)量類型,則轉(zhuǎn)換為NSValue對(duì)象并返回該對(duì)象。

  6. 如果所有其他操作都失敗,請(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ì)象值中所述):

  1. 按此順序查找第一個(gè)名為set<Key>_set<Key>的訪問(wèn)器。如果找到,使用輸入值(或者根據(jù)需要使用unwrapped值)調(diào)用它并完成。
  2. 如果找不到簡(jiǎn)單的訪問(wèn)器,并且類方法accessInstanceVariablesDirectly返回YES,則按該順序查找名為_<key>_is<key>、<key>is<key>的實(shí)例變量。如果找到,直接用輸入值(或未包裝值)設(shè)置變量并完成。
  3. 在找不到訪問(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ù)組:

  1. 查找一對(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下使用這些方法以獲得最佳性能。

  1. 如果對(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)該避免使用它。

  2. 如果既沒有找到可變數(shù)組方法,也沒有找到訪問(wèn)器,并且如果接收者的類直接響應(yīng)accessInstanceVariables返回Yes,則按該順序搜索名為_<key><key>的實(shí)例變量。

    如果找到這樣的實(shí)例變量,則返回一個(gè)代理對(duì)象,該對(duì)象將接收到的每個(gè)NSMutableArray消息轉(zhuǎn)發(fā)給實(shí)例變量的值,該值通常是NSMutableArray的實(shí)例或其子類之一。

  3. 如果所有其他操作都失敗,則在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í)行以下操作:

  1. 查找一對(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下使用這些方法以獲得最佳性能。

  2. 如果對(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)該避免使用它。

  3. 如果既沒有找到可變數(shù)組方法,也沒有找到訪問(wèn)器,并且如果接收者的類直接響應(yīng)accessInstanceVariables返回Yes,則按該順序搜索名為_<key><key>的實(shí)例變量。

    如果找到這樣的實(shí)例變量,則返回一個(gè)代理對(duì)象,該對(duì)象將接收到的每個(gè)NSMutableOrderedSet消息轉(zhuǎn)發(fā)給實(shí)例變量的值,該值通常是NSMutableOrderedSet的實(shí)例或其子類之一。

  4. 如果所有其他操作都失敗,則在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ù)組屬性的可變代理集:

  1. 搜索名為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>:等名稱的方法以獲得最佳性能(如果它們可用)。

  2. 如果mutableSetValueForKey:調(diào)用的接收者是托管對(duì)象,則搜索模式不會(huì)像對(duì)非托管對(duì)象那樣繼續(xù)。有關(guān)詳細(xì)信息,請(qǐng)參閱Core Data Programming Guide中的托管對(duì)象訪問(wèn)器方法。

  3. 如果找不到可變集方法,并且該對(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)該避免使用它。

  4. 如果找不到可變集方法和訪問(wèn)器方法,并且如果accessInstanceVariablesDirectly類方法返回YES,則按該順序搜索名稱類似于_<key><key>的實(shí)例變量。如果找到這樣的實(shí)例變量,則代理對(duì)象將收到的每個(gè)NSMutableSet消息轉(zhuǎn)發(fā)給實(shí)例變量的值,該值通常是NSMutableSet的實(shí)例或其子類之一。

  5. 如果所有其他操作都失敗,則返回的代理對(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方法返回為類定義的所有屬性的列表;toManyRelationshipKeystoOneRelationshipKeys方法返回定義為多對(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ì)象的引用。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容