鍵值編碼編程指南(Key-Value Coding Programming Guide)

開始

關(guān)于鍵值編碼

鍵值編碼是一種機制囚戚,通過NSKeyValueCoding非正式協(xié)議,對象采用這種機制提供對其屬性的間接訪問。當對象符合鍵值編碼時, 它的屬性可以使用字符串參數(shù)通過簡明褒侧、統(tǒng)一的消息接口進行尋址。這種間接訪問機制補充了實例變量及其關(guān)聯(lián)的訪問器方法提供的直接訪問。

通常使用訪問器方法獲取對對象屬性的訪問權(quán)限闷供。get 訪問器 (或 getter) 返回屬性的值烟央。set 訪問器 (或 setter) 設(shè)置屬性的值。在Objective-C中, 還可以直接訪問屬性內(nèi)部的實例變量歪脏。以上述任何一種方式訪問對象屬性都非常直截了當, 但需要調(diào)用屬性特定的方法或變量名疑俭。隨著屬性列表的增長或更改, 必須編寫訪問這些屬性的代碼。相反, 鍵值編碼兼容對象提供了一個在其所有屬性中一致的簡單消息傳遞接口婿失。

鍵值編碼是許多其他Cocoa技術(shù)的基礎(chǔ)概念, 如KVO钞艇、Cocoa bindingsCore DataAppleScript-ability豪硅。在某些情況下, 鍵值編碼還可以幫助簡化代碼哩照。

使用鍵值編碼兼容對象

對象通常采用鍵值編碼, 當它們 (直接或間接) 繼承NSObject時, 它們都采用了NSKeyValueCodin協(xié)議, 并為基本方法提供默認實現(xiàn)。此類對象使其他對象能夠通過簡潔的消息接口執(zhí)行以下操作:

  • 訪問對象屬性. 協(xié)議中指定的方法, 例如一般的 getter valueForKey:和常用的setter setValue:forKey:, 用于通過名稱或鍵訪問對象屬性 (參數(shù)化為字符串)懒浮。這些方法和相關(guān)方法的默認實現(xiàn)使用key來定位基礎(chǔ)數(shù)據(jù)并與之交互, 詳見訪問對象屬性.

  • 操作集合屬性. 配合對象集合屬性(如NSArray對象)使用的訪問器方法的默認實現(xiàn)與對象其他屬性的訪問器方法一樣飘弧。此外, 如果對象定義了屬性的集合訪問器方法, 則它允許對集合的內(nèi)容進行鍵值訪問。這通常比直接訪問效率更高, 并允許你通過標準化的接口處理自定義集合對象, 詳見訪問集合屬性.

  • 在集合對象上調(diào)用集合運算符. 在鍵值編碼兼容對象中訪問集合屬性時, 可以將集合運算符插入到鍵字符串中, 詳見使用集合運算符砚著。集合運算符指示NSKeyValueCoding getter 默認的實現(xiàn)去對集合執(zhí)行某些操作, 然后返回一個全新的次伶、過濾過的集合版本或單個表示某些集合特性的值 。

  • 訪問非對象屬性. 協(xié)議的默認實現(xiàn)支持檢測非對象屬性, 包括標量和結(jié)構(gòu), 并自動將它們包裝成對象和解包,詳見表示非對象值.此外, 該協(xié)議還聲明了一種方法, 允許兼容對象通過鍵值編碼接口在非對象屬性上設(shè)置nil值時為該情況提供適當?shù)牟僮鳌?/p>

  • 按鍵路徑訪問屬性. 當你有一個鍵值編碼兼容的對象的層級結(jié)構(gòu),你可以使用key path based方法在單次調(diào)用中獲取或設(shè)置層次結(jié)構(gòu)深處的值稽穆。

為對象采用鍵值編碼

為了使您自己的對象兼容鍵值編碼, 您可以確保它們遵循NSKeyValueCoding非正式協(xié)議并實現(xiàn)相應(yīng)的方法, 如valueForKey:作為一般的 getter 和setValue:forKey:作為一般的 setter冠王。幸運的是, 如上文所述, NSObject遵循此協(xié)議, 并為這些和其他基本方法提供了默認實現(xiàn)。因此, 如果從NSObject (或它的許多子類) 中派生對象, 則大部分工作已經(jīng)完成舌镶。

為了使默認方法執(zhí)行其工作, 您可以確保對象的訪問器方法和實例變量遵守某些定義良好的模式柱彻。這允許默認實現(xiàn)在響應(yīng)鍵值編碼消息時查找對象的屬性。然后, 您可以通過提供驗證方法和處理某些特殊情況來擴展和自定義鍵值編碼乎折。

Swift中的鍵值編碼

默認情況下, 繼承自NSObject或其子類的 Swift 對象绒疗,其屬性是支持鍵值編碼的。而在Objective-C 中, 屬性的訪問器和實例變量必須遵循某些模式, Swift 中的標準屬性聲明會自動保證這一點骂澄。另一方面, 許多協(xié)議的功能要么不相關(guān), 要么使用某些Objective-C中沒有的Swift 原生構(gòu)造和技術(shù)能夠更好地處理吓蘑。例如, 由于所有 Swift 屬性都是對象, 因此你永遠不會使用到默認實現(xiàn)中對非對象屬性的特殊處理。

雖然鍵值編碼協(xié)議方法直截了當?shù)胤g成Swift, 但本指南主要側(cè)重于Objective-C, 在這里您需要做更多的事來確保兼容, 而鍵值編碼通常最有用坟冲。在整個指南中都需要注意到在 Swift 中采取明顯不同方法的情況磨镶。

有關(guān)使用 swift 與Cocoa技術(shù)的詳細信息, 請參閱Using Swift with Cocoa and Objective-C (Swift 4.1)。有關(guān) swift 的完整說明, 請閱讀The Swift Programming Language (Swift 4.1).

其他依賴于鍵值編碼的Cocoa技術(shù)

鍵值編碼兼容的對象可以廣泛的應(yīng)用于依賴這種訪問的Cocoa技術(shù), 其中包括:

  • 鍵值觀察. 此機制使對象可以注冊由其他對象屬性中的更改驅(qū)動的異步通知, 詳見Key-Value Observing Programming Guide.

  • Cocoa bindings. 此技術(shù)集完全實現(xiàn)了模型-視圖-控制器范式, 其中模型封裝應(yīng)用程序數(shù)據(jù)健提、視圖顯示和編輯數(shù)據(jù)以及控制器在兩者之間進行協(xié)調(diào)琳猫。閱讀Cocoa Bindings Programming Topics了解有關(guān)Cocoa Bindings的更多信息。

  • Core Data. 此框架為與對象生命周期和對象圖管理 (包括持久性) 相關(guān)的常見任務(wù)提供了通用和自動解決方案私痹。您可以在Core Data Programming Guide中閱讀有關(guān)Core Data的信息脐嫂。.

  • AppleScript 這種腳本語言可以直接控制可腳本化的應(yīng)用程序和 macOS 的許多部分统刮。Cocoa的腳本支持利用鍵值編碼來獲取和設(shè)置可腳本化對象中的信息。NSScriptKeyValueCoding非正式協(xié)議中的方法為使用鍵值編碼提供了額外的功能, 包括按多值鍵中的索引獲取和設(shè)置鍵值, 并將鍵值強制 (或轉(zhuǎn)換) 為適當?shù)臄?shù)據(jù)類型账千。AppleScript Overview提供了 AppleScript 及其相關(guān)技術(shù)的高級別概述侥蒙。

鍵值編碼基本原理

訪問對象屬性

對象通常在其接口聲明中指定屬性, 這些屬性屬于以下幾個類別之一:

  • 屬性. 一些簡單的值, 例如標量(scalars)、字符串或布爾值匀奏。值對象(如NSNumber)和其他不可變類型(如NSColor) 也被視為屬性鞭衩。
  • 對一關(guān)系. 指具有自身屬性的可變對象。對象的屬性可以在對象本身沒有更改的情況下進行更改娃善。
  • 對多關(guān)系. 指集合對象论衍。你通常使用NSArrayNSSet的實例來保存此類集合, 自定義集合類也是可行的。

清單 2-1中聲明的BankAccount對象演示了每種類型的屬性聚磺。

清單 2-1BankAccount對象的屬性

@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

為了維護封裝, 對象通常為接口中的屬性提供了訪問器方法坯台。對象的作者可以顯式地編寫這些方法, 也可以依賴編譯器自動合成它們。無論哪種方式, 代碼的作者使用這些訪問器時都必須在編譯代碼之前將屬性名寫到代碼中咧最。訪問器方法的名稱成為使用它的代碼的靜態(tài)部分捂人。例如, 給定清單 2-1中聲明的銀行帳戶對象, 編譯器將合成一個可為myAccount實例調(diào)用的 setter:

[myAccount setCurrentBalance:@(100.0)];

這很直接, 但缺乏靈活性御雕。另一方面, 一個鍵值編碼兼容對象提供了一個更通用的機制來使用字符串標識符訪問對象的屬性矢沿。

使用鍵和鍵路徑標識對象的屬性

key是標識特定屬性的字符串酸纲。通常, 按照約定, 表示屬性的鍵key是屬性本身在代碼中顯示的名稱捣鲸。鍵key必須使用 ASCII 編碼, 不能包含空格, 通常以小寫字母開頭 (盡管有例外, 如在許多類中找到的URL屬性)掖棉。

因為清單 2-1中的BankAccount類是符合鍵值編碼的, 所以它能識別鍵(即其屬性的名稱)ownercurrentBalancetransactions 葛峻。您可以通過其鍵來設(shè)置值, 而不是調(diào)用setCurrentBalance:方法:

[myAccount setValue:@(100.0) forKey:@"currentBalance"];

實際上, 可以使用不同的鍵參數(shù)通過相同方法來設(shè)置myAccount對象的所有屬性。因為參數(shù)是字符串類型, 所以它可以在運行時操作變量巴比。

鍵路徑Key path是一個用點操作符.來分隔鍵的字符串, 用于指定要遍歷的對象屬性序列术奖。序列中第一個鍵的屬性相對于接收者, 每個后續(xù)鍵相對于上一個屬性的值進行計算。鍵路徑對于使用單個方法深入調(diào)用對象的層次結(jié)構(gòu)很有用轻绞。

例如, 應(yīng)用于銀行帳戶實例的鍵路徑owner.address.street是指存儲在銀行帳戶所有者地址中的街道字符串的值, 假設(shè)PersonAddress類也符合的鍵值編碼采记。

注意
在 Swift 中, 您可以使用#keyPath表達式, 而不是使用字符串來指示鍵或鍵路徑。這提供了編譯期間檢查的優(yōu)點, 詳見Using Swift with Cocoa and Objective-C (Swift 4.1) 中的Keys and Key Paths章節(jié)政勃。

使用鍵獲取屬性值

當對象遵循 NSKeyValueCoding非正式協(xié)議唧龄,則該對象支持鍵值編碼。從NSObject(提供了協(xié)議中基本方法的默認實現(xiàn))繼承的對象, 它會自動采用此協(xié)議的某些默認行為奸远。這樣的對象至少實現(xiàn)以下基本的key-based的 getter:

  • valueForKey: 返回由鍵參數(shù)命名的屬性的值既棺。如果根據(jù)訪問器搜索模式中描述的規(guī)則無法找到由鍵命名的屬性, 則該對象將自己發(fā)送valueForUndefinedKey:消息。valueForUndefinedKey的默認實現(xiàn)將拋出NSUndefinedKeyException異常, 但子類可以重寫此行為并更優(yōu)雅地處理此情況懒叛。

  • valueForKeyPath: 返回相對于接收者的指定鍵路徑的值丸冕。鍵路徑序列中指定鍵所對應(yīng)的對象如果不是鍵值編碼兼容的, 即valueForKey:的默認實現(xiàn)無法找到訪問器方法-那么就會接收valueForUndefinedKey:消息。

  • dictionaryWithValuesForKeys: 返回相對于接收者的一組鍵所對應(yīng)的值薛窥。該方法為數(shù)組中的每個鍵調(diào)用valueForKey: 胖烛。返回的NSDictionary包含數(shù)組中所有鍵的值。

注意
集合對象 (如NSArray拆檬、 NSSetNSDictionary) 不能包含nil作為值洪己。而是使用NSNull對象表示nil值。NSNull提供一個表示對象屬性的nil值的單個實例竟贯。dictionaryWithValuesForKeys:的默認實現(xiàn)和相關(guān)的setValuesForKeysWithDictionary:會在NSNull (在字典參數(shù)中) 和nil(在存儲的屬性中)之間進行自動轉(zhuǎn)換 答捕。

當您使用鍵路徑來尋址屬性時, 如果鍵路徑中的最后一個鍵是一對多關(guān)系 (即引用集合), 則返回的值是一個集合, 其中包含對多關(guān)系的鍵右側(cè)的鍵的所有值。例如, 請求鍵路徑 "transactions.payee" 的值返回包含所有transaction對象中payee對象的數(shù)組屑那。這也適用于鍵路徑中的多個數(shù)組拱镐。鍵路徑accounts.transactions.payee返回包含所有帳戶中所有交易記錄的所有收款人對象的數(shù)組艘款。

使用鍵設(shè)置屬性值

getter一樣, 鍵值編碼兼容對象還提供了一小組具有默認行為的廣義 setter, 它基于在NSObject中對NSKeyValueCoding協(xié)議的實現(xiàn):

  • setValue:forKey: 設(shè)置相對于接收到給定值消息的對象的指定鍵的值。setValue:forKey:的默認實現(xiàn)會自動對表示標量和結(jié)構(gòu)的NSNumberNSValue對象執(zhí)行unwarp操作沃琅,并將它們設(shè)置到相應(yīng)的屬性中哗咆。有關(guān)warpunwarp的詳細信息, 詳見表示非對象值

如果接收setter調(diào)用的對象中沒有對應(yīng)指定鍵的屬性益眉,該對象將自己發(fā)送一個[setValue:forUndefinedKey:]消息晌柬。setValue:forUndefinedKey:的默認實現(xiàn)將拋出NSUndefinedKeyException異常。但是, 子類可以重寫此方法以自定義方式處理請求郭脂。

  • setValue:forKeyPath: 在相對于接收者的指定鍵路徑上設(shè)置給定值年碘。鍵路徑序列中指定鍵所對應(yīng)的對象如果不是鍵值編碼兼容的,將會收到setValue:forUndefinedKey:消息展鸡。

  • setValuesForKeysWithDictionary: 將指定字典中的值設(shè)置到接收者的屬性中, 使用字典鍵標識屬性屿衅。默認實現(xiàn)調(diào)用每個鍵值對的setValue:forKey: , 根據(jù)需要用nil替換NSNull對象。

在默認實現(xiàn)中, 當您嘗試將非對象屬性設(shè)置為nil值時, 鍵值編碼兼容對象將自己發(fā)送一個setNilValueForKey:消息莹弊。setNilValueForKey:的默認實現(xiàn)將拋出[NSInvalidArgumentException]異常, 但對象可能會重寫此行為以替換默認值或標記值, 詳見處理非對象值涤久。

使用鍵簡化對象訪問

想知道基于鍵的 gettersetter 如何簡化代碼, 請查看下面的示例。在 macOS 中, NSTableViewNSOutlineView對象將標識符字符串與每列關(guān)聯(lián)起來忍弛。如果表的模型對象不是符合鍵值編碼的, 則表的數(shù)據(jù)源方法將強制檢查每個列標識符, 依次查找要返回的正確屬性, 如清單 2-2所示响迂。此外, 在將來, 當您向模型中添加另一個屬性時, 在本例中為Person 對象, 還必須重新訪問數(shù)據(jù)源方法, 添加另一個條件來測試新屬性并返回相關(guān)值.

清單 2-2不基于鍵值編碼的數(shù)據(jù)源方法的實現(xià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;
}

另一方面,清單 2-3展示了相同數(shù)據(jù)源的方法的一個更緊湊的實現(xiàn), 該數(shù)據(jù)源方法使用的是鍵值編碼兼容的Person對象。僅使用valueForKey: getter, 數(shù)據(jù)源方法將使用列標識符作為鍵返回適當?shù)闹稻缯帧3烁痰臅r間外, 它還更通用, 因為在以后添加新列時, 只要列標識符始終與模型對象的屬性名稱匹配, 它就會繼續(xù)保持不變栓拜。

清單 2-3基于鍵值編碼的數(shù)據(jù)源方法的實現(xiàn)

- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row {
    return [[self.people objectAtIndex:row] valueForKey:[column identifier]];
}

訪問集合屬性

鍵值編碼兼容對象以與公開其他屬性相同的方式公開其對多屬性座泳。您可以像使用valueForKey:setValue:forKey: (或它們的鍵路徑等同方法) 一樣獲取或設(shè)置集合對象惠昔。但是, 當您要操作這些集合的內(nèi)容時, 使用協(xié)議定義的可變代理方法通常是最有效的。

該協(xié)議為集合對象訪問定義了三種不同的代理方法, 每個都具有一個鍵和一個鍵路徑變體方法:

  • mutableArrayValueForKey:mutableArrayValueForKeyPath:

    這些方法返回的代理對象的行為類似于NSMutableArray對象挑势。

  • mutableSetValueForKey:mutableSetValueForKeyPath:

    這些方法返回的代理對象的行為類似于NSMutableSet對象镇防。

  • mutableOrderedSetValueForKey:mutableOrderedSetValueForKeyPath:

    這些方法返回的代理對象的行為類似于NSMutableOrderedSet對象。

當您對代理對象進行操作潮饱、向其添加對象来氧、從其中移除對象或替換其中的對象時, 協(xié)議的默認實現(xiàn)將相應(yīng)地修改基礎(chǔ)屬性。這比使用valueForKey:得到一個不可變集合對象香拉,創(chuàng)建一個修改了內(nèi)容的可變集合對象, 然后將其存儲回帶有setValue:forKey:消息的對象更有效啦扬。在許多情況下, 它也比直接使用可變屬性更有效。這些方法提供了對集合對象中保存的對象保持鍵值觀察遵從性的額外好處 (請參見Key-Value Observing Programming Guide以了解詳細信息)凫碌。

使用集合運算符

當你發(fā)送向鍵值編碼兼容的對象發(fā)送valueForKeyPath:消息時, 可以在鍵路徑中嵌入集合運算符扑毡。集合運算符是前面有 at 符號 (@) 的一小部分關(guān)鍵字列表, 它指定了getter在返回之前以某種方式操作數(shù)據(jù)。NSObject提供的valueForKeyPath:的默認實現(xiàn)實現(xiàn)了此行為盛险。

當鍵路徑包含集合運算符時, 運算符前面的鍵路徑的任何部分 (稱為左鍵路徑) 指示相對于消息接收者需要去操作的集合瞄摊。如果將消息直接發(fā)送到集合對象 (如NSArray實例), 則可以省略左鍵路徑勋又。

圖 4-1運算符鍵路徑格式

image.png

集合運算符展示了三種基本行為類型:

  • 聚合運算符以某種方式合并集合的對象, 并返回一個通常與在右鍵路徑中命名的屬性的數(shù)據(jù)類型相匹配的單個對象。@count運算符是一個特例换帜,它沒有右鍵路徑, 并且始終返回NSNumber實例楔壤。

  • 數(shù)組運算符返回一個NSArray實例, 其中包含命名集合中保存的對象的某些子集。

  • 嵌套運算符處理包含其他集合的集合, 并返回一個NSArrayNSSet實例 (根據(jù)運算符), 它將嵌套集合的對象以某種方式組合在一起惯驼。

示例數(shù)據(jù)

下面的說明包括演示如何調(diào)用每個運算符的代碼段以及這樣做的結(jié)果蹲嚣。這依賴于BankAccount類 (在[列表 2-1]中顯示), 它包含Transaction對象的數(shù)組。其中每一個都代表一個簡單的checkbook條目, 如清單 4-1中所聲明的那樣祟牲。

清單 4-1Transaction對象的接口聲明

@interface Transaction : NSObject
 
@property (nonatomic) NSString* payee;   // To whom
@property (nonatomic) NSNumber* amount;  // How much
@property (nonatomic) NSDate* date;      // When
 
@end

為了進行討論, 假定BankAccount實例具有一個事務(wù)數(shù)組, 其中填充了表 4-1中顯示的數(shù)據(jù), 并使示例從BankAccount對象內(nèi)部調(diào)用端铛。

表 4-1Transactions對象的示例數(shù)據(jù)

payee amount date
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

聚合運算符

聚合運算符處理arrayset屬性, 生成一個反映集合的某些方面的單個值。

@avg

當指定@avg運算符時, valueForKeyPath:讀取集合中每個元素的右鍵路徑指定的屬性, 將其轉(zhuǎn)換為double (nil值用0替代), 并計算算術(shù)平均值疲眷。然后返回存儲在NSNumber實例中的結(jié)果禾蚕。

獲取表 4-1中示例數(shù)據(jù)之間的平均交易記錄金額:

NSNumber *transactionAverage = [self.transactions valueForKeyPath:@"@avg.amount"];

transactionAverage的格式化的結(jié)果為 $ 456.54。

@count

指定@count運算符時, valueForKeyPath:返回一個包含集合中的對象個數(shù)的NSNumber實例狂丝。右鍵路徑 (如果存在) 將被忽略换淆。

在transactions中獲取Transaction對象的數(shù)目:

NSNumber *numberOfTransactions = [self.transactions valueForKeyPath:@"@count"];

numberOfTransactions的值為13。

@max

指定@max運算符時, valueForKeyPath:在由右鍵路徑命名的集合項之間進行搜索, 并返回最大值几颜。搜索使用compare:方法進行比較, 許多基礎(chǔ)類 (如NSNumber類) 中都有定義倍试。因此, 由右鍵路徑指示的屬性必須持有對此消息有意義響應(yīng)的對象。搜索忽略值為nil的集合項蛋哭。

在表 4-1中列出的交易記錄中, 獲取日期值 (即最新交易記錄的日期) 的最大數(shù)量:

NSDate *latestDate = [self.transactions valueForKeyPath:@"@max.date"];

latestDate的值為 Jul 15, 2016.

@min

指定@min運算符時, valueForKeyPath:在由右鍵路徑命名的集合項之間進行搜索, 并返回最小值县习。搜索使用compare:方法進行比較, 許多基礎(chǔ)類 (如NSNumber類) 中都有定義。因此, 由右鍵路徑指示的屬性必須持有對此消息有意義響應(yīng)的對象谆趾。搜索忽略值為nil的集合項躁愿。

在表 4-1中列出的事務(wù)中, 獲取日期值 (即最早的事務(wù)的日期) 的最短時間厕妖。

NSDate *earliestDate = [self.transactions valueForKeyPath:@"@min.date"];

earliestDate的值為 Dec 1, 2015.

@sum

指定@sum運算符時, valueForKeyPath:讀取集合中每個元素的右鍵路徑指定的屬性, 將其轉(zhuǎn)換為double (nil值替換為 0), 并計算總和野哭。然后返回存儲在NSNumber實例中的結(jié)果。

獲取表 4-1中示例數(shù)據(jù)之間的交易記錄金額的總和:

NSNumber *amountSum = [self.transactions valueForKeyPath:@"@sum.amount"];

amountSum的結(jié)果為 $ 5935.00缸托。

數(shù)組運算符

數(shù)組運算符使valueForKeyPath:返回與右鍵路徑指示的特定對象集相對應(yīng)的對象數(shù)組跷叉。

重要
在使用數(shù)組運算符時, 如果有任何葉(leaf)對象為nil, 則valueForKeyPath:方法將引發(fā)異常逸雹。

@distinctUnionOfObjects

指定@distinctUnionOfObjects運算符時, valueForKeyPath:將創(chuàng)建并返回一個數(shù)組, 其中包含與右鍵路徑指定的屬性對應(yīng)的集合的不同對象。

獲取transactions中的交易記錄的payee屬性值的集合, 但省略了重復值:

NSArray *distinctPayees = [self.transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"];

生成的distinctPayees數(shù)組包含以下每一個字符串實例:Car Loan, General Cable, Animal Hospital, Green Power, Mortgage云挟。

注意
@unionOfObjects運算符提供類似的行為, 但不刪除重復的對象梆砸。

@unionOfObjects

指定@unionOfObjects運算符時, valueForKeyPath:將創(chuàng)建并返回一個數(shù)組, 其中包含與由右鍵路徑指定的屬性對應(yīng)的集合的所有對象。與@distinctUnionOfObjects不同, 不刪除重復對象园欣。

獲取transactions中的交易記錄的payee屬性值的集合:

NSArray *payees = [self.transactions valueForKeyPath:@"@unionOfObjects.payee"];

生成的payees數(shù)組包含以下字符串:Green Power, Green Power, Green Power, Car Loan, Car Loan, Car Loan, General Cable, General Cable, General Cable, Mortgage, Mortgage, Mortgage, Animal Hospital.記錄了重復值帖世。

注意
@distinctUnionOfArrays運算符類似, 但移除重復對象。

嵌套運算符

嵌套運算符對嵌套集合進行操作, 集合本身的每個條目都包含一個集合俊庇。

重要
如果在使用嵌套運算符時, 有任何葉(leaf)對象為nil, 則valueForKeyPath:方法將引發(fā)異常狮暑。

對于下面的說明, 請看第二個稱為moreTransactions的數(shù)據(jù)數(shù)組, 其中填充了表 4-2中的數(shù)據(jù), 并與原來的transactions數(shù)組一起插入嵌套數(shù)組:

NSArray* moreTransactions = @[<# transaction data #>];
NSArray* arrayOfArrays = @[self.transactions, moreTransactions];

表 4-2 moreTransactions數(shù)組中假設(shè)的Transaction數(shù)據(jù)

payee amount date
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 Sho $600.00 Jun 14, 2016
@distinctUnionOfArrays

指定@distinctUnionOfArrays運算符時, valueForKeyPath:創(chuàng)建并返回一個數(shù)組, 其中包含與右鍵路徑指定的屬性相對應(yīng)的所有集合的組合的不同對象鸡挠。.

arrayOfArrays中的所有數(shù)組中獲取payee屬性的不同值:

NSArray *collectedDistinctPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"];

生成的collectedDistinctPayees數(shù)組包含以下值: Hobby Shop, Mortgage, Animal Hospital, Second Mortgage, Car Loan, General Cable - Cottage, General Cable, Green Power

注意
@unionOfArrays運算符類似, 但不移除重復對象搬男。

指定@unionOfArrays運算符時, valueForKeyPath:創(chuàng)建并返回一個數(shù)組, 其中包含與由右鍵路徑指定的屬性相對應(yīng)的所有集合的組合的所有對象, 而不刪除重復項拣展。

arrayOfArrays中的所有數(shù)組中獲取payee屬性的值:

NSArray *collectedPayees = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.payee"];

生成的collectedPayees數(shù)組包含以下值: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

注意
@distinctUnionOfArrays運算符類似, 但移除重復對象缔逛。

@distinctUnionOfSets

當指定@distinctUnionOfSets運算符時, valueForKeyPath:創(chuàng)建并返回一個NSSet對象, 其中包含與由右鍵路徑所指定的屬性相對應(yīng)的所有集合組合的不同對象备埃。

此運算符的行為與@distinctUnionOfArrays類似, 只是它需要一個NSSet實例, 其中包含對象的NSSet實例, 而不是NSArray實例中包含NSArray實例。此外, 它還返回一個NSSet實例褐奴。假設(shè)示例數(shù)據(jù)已存儲在集合而不是數(shù)組中, 則示例調(diào)用和結(jié)果與@distinctUnionOfArrays中顯示的相同按脚。.

驗證屬性

鍵值編碼協(xié)議定義了支持屬性驗證的方法。正如使用基于鍵的訪問器讀取和寫入鍵值編碼兼容對象的屬性一樣, 也可以按鍵 (或鍵路徑) 驗證屬性敦冬。當您調(diào)用validateValue:forKey:error:(或validateValue:forKeyPath:error:) 方法時, 協(xié)議的默認實現(xiàn)將搜索接收驗證消息的對象 (或在鍵路徑的末尾的對象), 該方法的名稱與模式validate<Key>:error:相匹配辅搬。如果對象沒有此類方法, 則默認情況下驗證成功, 默認實現(xiàn)返回YES.當存在屬性特定的驗證方法時, 默認實現(xiàn)將返回調(diào)用該方法的結(jié)果。

注意
您通常僅在Objective-C中使用此處描述的驗證脖旱。在 Swift 中, 通過依賴 optionals 和強類型檢查的編譯器支持, 可以更便捷地處理屬性驗證, 同時使用內(nèi)置的 willSet 和 didSet 屬性觀察器來測試任何運行時 API 協(xié)定, 詳見
The Swift Programming Language (Swift 4.1)
Property Observers章節(jié)對willSet didSet的描述堪遂。

由于屬性特定的驗證方法通過引用的方式接收值和錯誤參數(shù), 因此驗證有三種可能的結(jié)果:

  1. 驗證方法認為值對象有效并返回YES而不改變值或錯誤。
  2. 驗證方法認為值對象無效, 但選擇不更改它萌庆。在這種情況下, 該方法返回NO并將錯誤引用 (如果調(diào)用方提供) 設(shè)置為指示失敗原因的NSError對象溶褪。
  3. 驗證方法認為值對象無效, 但創(chuàng)建一個新的、有效的替換項践险。在這種情況下, 該方法返回YES同時使錯誤對象不被觸及猿妈。返回之前, 該方法修改值引用以指向新值對象。當它進行修改時, 該方法總是創(chuàng)建一個新對象, 而不是修改舊值, 即使 value 對象是可變的巍虫。

清單 6-1顯示了如何調(diào)用name字符串的驗證的示例彭则。

Person* person = [[Person alloc] init];
NSError* error;
NSString* name = @"John";
if (![person validateValue:&name forKey:@"name" error:&error]) {
    NSLog(@"%@",error);
}

自動驗證

通常, 鍵值編碼協(xié)議及其默認實現(xiàn)都不定義自動執(zhí)行驗證的任何機制。相反, 您可以在您的應(yīng)用程序中使用適合的驗證方法垫言。

某些其他Cocoa技術(shù)在某些情況下會自動進行驗證贰剥。例如, 當保存托管對象上下文時, Core Data自動執(zhí)行驗證 (詳見Core Data Programming Guide)。此外, 在 macOS 中, Cocoa Bindings 允許您指定驗證是否自動發(fā)生 (請閱讀Cocoa Bindings Programming Topics了解有關(guān)Cocoa Bindings的更多信息筷频。)。

訪問器搜索模式

NSObject提供的NSKeyValueCoding協(xié)議的默認實現(xiàn)將基于鍵的訪問器調(diào)用映射到對象的基礎(chǔ)屬性, 使用一組明確定義的規(guī)則前痘。這些協(xié)議方法使用鍵參數(shù)搜索其自己的對象實例, 以查找訪問器凛捏、實例變量以及遵循某些命名約定的相關(guān)方法。盡管您很少修改此默認搜索, 但了解它的工作原理, 對于跟蹤鍵值編碼對象的行為以及使您自己的對象兼容是很有幫助的芹缔。

注意
本節(jié)中的描述使用<key ><key >作為在一個鍵值編碼協(xié)議方法中出現(xiàn)的作為參數(shù)的鍵串的占位符坯癣,然后該方法被用作二次方法調(diào)用或變量名稱查找的一部分。映射的屬性名稱遵循占位符的情況最欠。例如, 對于 getter <key>is<Key>, 名為 "hidden" 的屬性映射為hiddenisHidden.

基本的Getter搜索模式

valueForKey:的默認實現(xiàn), 給定一個key參數(shù)作為輸入, 執(zhí)行以下過程, 從接收valueForKey調(diào)用的類實例中進行操作:示罗。

  1. 在實例中搜索找到第一個訪問器方法, 其名稱類似于get<Key>惩猫、 <key>is<Key>_<key>, 按此順序蚜点。如果找到, 則調(diào)用它, 然后繼續(xù)執(zhí)行步驟5以得到結(jié)果轧房。否則繼續(xù)執(zhí)行下一步。

  2. 如果找不到簡單訪問器方法, 則在實例中搜索其名稱與模式countOf<Key>objectIn<Key>AtIndex: (對應(yīng)于NSArray類中定義的基本方法)和<key>AtIndexes:(對應(yīng)于NSArray類中的objectsAtIndexes:方法)方法绍绘。

    如果第一個方法和至少其他兩個方法中的一個方法被找到, 則創(chuàng)建一個能夠響應(yīng)所有NSArray方法的集合代理對象奶镶,并返回。否則, 請繼續(xù)執(zhí)行步驟3陪拘。

    代理對象隨后將接收到的任何NSArray消息轉(zhuǎn)換為countOf<Key>厂镇、 objectIn<Key>AtIndex:<key>AtIndexes:消息的某些組合到創(chuàng)建它的鍵值編碼兼容對象。如果原始對象還實現(xiàn)了一個名為 "get<Key>:range:" 的可選方法, 則代理對象在適當時也會使用它左刽。實際上, 與鍵值編碼兼容對象一起工作的代理對象允許基礎(chǔ)屬性擁有像NSArray對象一樣的行為, 即使它不是捺信。

  3. 如果找不到簡單訪問器方法或數(shù)組訪問方法組, 則查找名為countOf<Key>enumeratorOf<Key>memberOf<Key>: (對應(yīng)NSSet類中定義的三種基本方法)欠痴。

    如果三個方法找都被找到了, 則創(chuàng)建一個能夠響應(yīng)所有NSSet方法的集合代理對象残黑,并返回。否則, 繼續(xù)執(zhí)行步驟4斋否。

    此代理對象隨后將其接收到的任何NSSet消息轉(zhuǎn)換為countOf<Key>梨水、 enumeratorOf<Key>memberOf<Key>:消息的某些組合到創(chuàng)建它的對象的中。實際上, 與鍵值編碼兼容對象一起工作的代理對象允許基礎(chǔ)屬性擁有像NSSet一樣的行為, 即使它不是茵臭。

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

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

    如果該值是NSNumber所支持的標量類型, 則將其存儲在NSNumber實例中并返回摩钙。
    如果結(jié)果是 NSNumber 不支持的標量類型, 則轉(zhuǎn)換為NSValue對象, 然后返回它。

  6. 如果所有其他操作都失敗, 則調(diào)用valueForUndefinedKey:查辩。默認情況下, 這會引發(fā)異常, 但NSObject的子類可能會提供特定于鍵的行為胖笛。

基本的Setter搜索模式

setValue:forKey:的默認實現(xiàn), 給定keyvalue參數(shù)作為輸入, 在接收調(diào)用的對象內(nèi)嘗試將名為key的屬性設(shè)置為value(或者, 對于非對象屬性, 則為unwarp value, 詳見表示非對象值) , 使用以下過程:

  1. 按照順序查找第一個名為set<Key>:_set<Key>的訪問器。如果找到, 用輸入值 (或根據(jù)需要unwrap value) 調(diào)用它, 然后完成宜岛。
  2. 如果找不到簡單訪問器, 并且類方法accessInstanceVariablesDirectly返回YES, 則查找具有_<key>长踊、 _is<Key>、 <key>或is<Key>的名稱的實例變量, 按該排列順序萍倡。如果找到, 則直接使用輸入值 (或unwrap value) 設(shè)置變量并完成身弊。
    在找不到訪問器或?qū)嵗兞繒r, 調(diào)用setValue:forUndefinedKey:。默認情況下, 這會引發(fā)異常, 但NSObject的子類可能會提供特定于鍵的行為。

可變數(shù)組的搜索模式

mutableArrayValueForKey:的默認實現(xiàn), 給定一個key參數(shù)作為輸入, 在接收訪問器調(diào)用的對象內(nèi)為名為key的屬性返回一個可變的代理數(shù)組, 使用以下過程:

  1. 查找一對方法阱佛,如insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex: (對應(yīng)于NSMutableArray的基本方法方法insertObject:atIndex:removeObjectAtIndex: ), 或insert<Key>:atIndexes:remove<Key>AtIndexes: (對應(yīng)于NSMutableArrayinsertObjects:atIndexes:removeObjectsAtIndexes:方法)帖汞。

    如果對象有至少一個插入方法和至少一個刪除方法, 則返回一個代理對象,該代理對象響應(yīng)NSMutableArray消息凑术,并將insertObject:in<Key>AtIndex:, removeObjectFrom<Key>AtIndex:, insert<Key>:atIndexes:, and remove<Key>AtIndexes:消息的一些組合發(fā)送給mutableArrayValueForKey:的原始接受者翩蘸。

    當接收mutableArrayValueForKey:消息的對象還實現(xiàn)一個可選的替換對象方法, 其名稱類似于replaceObjectIn<Key>AtIndex:withObject:replace<Key>AtIndexes:with<Key>:, 代理對象在適合最佳性能時也會利用這些功能。

  2. 如果對象沒有可變數(shù)組方法, 則改用其名稱與模式set<Key>:相匹配的訪問器方法麦萤。在這種情況下, 通過向mutableArrayValueForKey:的原始接收者發(fā)出set<Key>:消息, 返回響應(yīng)NSMutableArray消息的代理對象鹿鳖。

注意
此步驟中描述的機制比上一步的效率要低得多, 因為它可能涉及重復創(chuàng)建新的集合對象, 而不是修改現(xiàn)有的。因此, 在設(shè)計自己的鍵值編碼兼容對象時, 通常應(yīng)避免這種情況壮莹。

  1. 如果未找到可變數(shù)組方法或訪問器, 并且接收者的類響應(yīng)YES對于accessInstanceVariablesDirectly, 則按照該順序搜索具有_<key><key>等名稱的實例變量翅帜。

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

  2. 如果所有其他操作失敗, 則返回一個可變的集合代理對象, 它將發(fā)出setValue:forUndefinedKey:消息到 mutableArrayValueForKey 的原始接收者命满, 每當它收到NSMutableArray`消息涝滴。

    setValue:forUndefinedKey:的默認實現(xiàn)會拋出NSUndefinedKeyException異常, 但子類可能會重寫此行為。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胶台,一起剝皮案震驚了整個濱河市歼疮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诈唬,老刑警劉巖韩脏,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異铸磅,居然都是意外死亡赡矢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門阅仔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吹散,“玉大人,你說我怎么就攤上這事八酒】彰瘢” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵羞迷,是天一觀的道長界轩。 經(jīng)常有香客問我,道長闭树,這世上最難降的妖魔是什么耸棒? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮报辱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己碍现,他們只是感情好幅疼,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著昼接,像睡著了一般爽篷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上慢睡,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天逐工,我揣著相機與錄音,去河邊找鬼漂辐。 笑死泪喊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的髓涯。 我是一名探鬼主播袒啼,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼纬纪!你這毒婦竟也來了蚓再?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤包各,失蹤者是張志新(化名)和其女友劉穎摘仅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體问畅,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡娃属,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了按声。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膳犹。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖签则,靈堂內(nèi)的尸體忽然破棺而出须床,到底是詐尸還是另有隱情,我是刑警寧澤渐裂,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布豺旬,位于F島的核電站,受9級特大地震影響柒凉,放射性物質(zhì)發(fā)生泄漏族阅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一膝捞、第九天 我趴在偏房一處隱蔽的房頂上張望坦刀。 院中可真熱鬧,春花似錦、人聲如沸鲤遥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盖奈。三九已至混坞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钢坦,已是汗流浹背究孕。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留爹凹,地道東北人厨诸。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像逛万,于是被迫代替她去往敵國和親泳猬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

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