開始
關(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 bindings
、Core Data
和 AppleScript-ability
豪硅。在某些情況下, 鍵值編碼還可以幫助簡化代碼哩照。
使用鍵值編碼兼容對象
對象通常采用鍵值編碼, 當它們 (直接或間接) 繼承NSObject
時, 它們都采用了NSKeyValueCodin
協(xié)議, 并為基本方法提供默認實現(xiàn)。此類對象使其他對象能夠通過簡潔的消息接口執(zhí)行以下操作:
訪問對象屬性. 協(xié)議中指定的方法, 例如一般的 getter
valueForKey:
和常用的settersetValue: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)系. 指集合對象论衍。你通常使用
NSArray
或NSSet
的實例來保存此類集合, 自定義集合類也是可行的。
清單 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
類是符合鍵值編碼的, 所以它能識別鍵(即其屬性的名稱)owner
、currentBalance
和transactions
葛峻。您可以通過其鍵來設(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è)Person
和Address
類也符合的鍵值編碼采记。
注意
在 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
拆檬、NSSet
和NSDictionary
) 不能包含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)的NSNumber
和NSValue
對象執(zhí)行unwarp
操作沃琅,并將它們設(shè)置到相應(yīng)的屬性中哗咆。有關(guān)warp
和unwarp
的詳細信息, 詳見表示非對象值。
如果接收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]
異常, 但對象可能會重寫此行為以替換默認值或標記值, 詳見處理非對象值涤久。
使用鍵簡化對象訪問
想知道基于鍵的 getter
和 setter
如何簡化代碼, 請查看下面的示例。在 macOS 中, NSTableView
和NSOutlineView
對象將標識符字符串與每列關(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運算符鍵路徑格式
集合運算符展示了三種基本行為類型:
聚合運算符以某種方式合并集合的對象, 并返回一個通常與在右鍵路徑中命名的屬性的數(shù)據(jù)類型相匹配的單個對象。
@count
運算符是一個特例换帜,它沒有右鍵路徑, 并且始終返回NSNumber
實例楔壤。數(shù)組運算符返回一個
NSArray
實例, 其中包含命名集合中保存的對象的某些子集。嵌套運算符處理包含其他集合的集合, 并返回一個
NSArray
或NSSet
實例 (根據(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 |
聚合運算符
聚合運算符處理array
或set
屬性, 生成一個反映集合的某些方面的單個值。
@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é)果:
- 驗證方法認為值對象有效并返回YES而不改變值或錯誤。
- 驗證方法認為值對象無效, 但選擇不更改它萌庆。在這種情況下, 該方法返回NO并將錯誤引用 (如果調(diào)用方提供) 設(shè)置為指示失敗原因的NSError對象溶褪。
- 驗證方法認為值對象無效, 但創(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" 的屬性映射為hidden
和isHidden
.
基本的Getter搜索模式
valueForKey:
的默認實現(xiàn), 給定一個key
參數(shù)作為輸入, 執(zhí)行以下過程, 從接收valueForKey
調(diào)用的類實例中進行操作:示罗。
在實例中搜索找到第一個訪問器方法, 其名稱類似于
get<Key>
惩猫、<key>
、is<Key>
或_<key>
, 按此順序蚜点。如果找到, 則調(diào)用它, 然后繼續(xù)執(zhí)行步驟5以得到結(jié)果轧房。否則繼續(xù)執(zhí)行下一步。-
如果找不到簡單訪問器方法, 則在實例中搜索其名稱與模式
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
對象一樣的行為, 即使它不是捺信。 -
如果找不到簡單訪問器方法或數(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
一樣的行為, 即使它不是茵臭。 如果找不到簡單訪問器方法或集合訪問方法組, 并且如果接收者的類方法
accessInstanceVariablesDirectly
返回YES
, 搜索名為_<key>
疫诽、_is<Key>
、<key>
或is<Key>
的實例變量, 按該順序進行旦委。如果找到, 則直接獲取實例變量的值, 然后繼續(xù)執(zhí)行步驟5奇徒。否則, 繼續(xù)執(zhí)行步驟6。-
如果檢索到的屬性值是對象指針, 則只需返回結(jié)果缨硝。
如果該值是
NSNumber
所支持的標量類型, 則將其存儲在NSNumber
實例中并返回摩钙。
如果結(jié)果是 NSNumber 不支持的標量類型, 則轉(zhuǎn)換為NSValue
對象, 然后返回它。 如果所有其他操作都失敗, 則調(diào)用
valueForUndefinedKey:
查辩。默認情況下, 這會引發(fā)異常, 但NSObject
的子類可能會提供特定于鍵的行為胖笛。
基本的Setter搜索模式
setValue:forKey:
的默認實現(xiàn), 給定key
和value
參數(shù)作為輸入, 在接收調(diào)用的對象內(nèi)嘗試將名為key
的屬性設(shè)置為value
(或者, 對于非對象屬性, 則為unwarp value
, 詳見表示非對象值) , 使用以下過程:
- 按照順序查找第一個名為
set<Key>:
或_set<Key>
的訪問器。如果找到, 用輸入值 (或根據(jù)需要unwrap value
) 調(diào)用它, 然后完成宜岛。 - 如果找不到簡單訪問器, 并且類方法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ù)組, 使用以下過程:
-
查找一對方法阱佛,如
insertObject:in<Key>AtIndex:
和removeObjectFrom<Key>AtIndex:
(對應(yīng)于NSMutableArray
的基本方法方法insertObject:atIndex:
和removeObjectAtIndex:
), 或insert<Key>:atIndexes:
和remove<Key>AtIndexes:
(對應(yīng)于NSMutableArray
的insertObjects:atIndexes:
和removeObjectsAtIndexes:
方法)帖汞。如果對象有至少一個插入方法和至少一個刪除方法, 則返回一個代理對象,該代理對象響應(yīng)
NSMutableArray
消息凑术,并將insertObject:in<Key>AtIndex:
,removeObjectFrom<Key>AtIndex:
,insert<Key>:atIndexes:
, andremove<Key>AtIndexes:
消息的一些組合發(fā)送給mutableArrayValueForKey:
的原始接受者翩蘸。當接收
mutableArrayValueForKey:
消息的對象還實現(xiàn)一個可選的替換對象方法, 其名稱類似于replaceObjectIn<Key>AtIndex:withObject:
或replace<Key>AtIndexes:with<Key>:
, 代理對象在適合最佳性能時也會利用這些功能。 如果對象沒有可變數(shù)組方法, 則改用其名稱與模式set<Key>:相匹配的訪問器方法麦萤。在這種情況下, 通過向
mutableArrayValueForKey:
的原始接收者發(fā)出set<Key>:
消息, 返回響應(yīng)NSMutableArray
消息的代理對象鹿鳖。
注意
此步驟中描述的機制比上一步的效率要低得多, 因為它可能涉及重復創(chuàng)建新的集合對象, 而不是修改現(xiàn)有的。因此, 在設(shè)計自己的鍵值編碼兼容對象時, 通常應(yīng)避免這種情況壮莹。
-
如果未找到可變數(shù)組方法或訪問器, 并且接收者的類響應(yīng)
YES
對于accessInstanceVariablesDirectly
, 則按照該順序搜索具有_<key>
或<key>
等名稱的實例變量翅帜。如果找到了這樣的實例變量, 則返回一個代理對象, 它將接收到的每個
NSMutableArray
消息轉(zhuǎn)發(fā)給實例變量的值, 這通常是NSMutableArray
或其子類的實例。 -
如果所有其他操作失敗, 則返回一個可變的集合代理對象, 它將發(fā)出
setValue:forUndefinedKey:
消息到mutableArrayValueForKey
的原始接收者命满, 每當它收到
NSMutableArray`消息涝滴。setValue:forUndefinedKey:
的默認實現(xiàn)會拋出NSUndefinedKeyException異常, 但子類可能會重寫此行為。