[譯]2.1-Key-Value Coding Programming Guide 官方文件第二部分

taoZen發表於2018-11-18

Key-Value Coding Programming Guide 官方文件第二部分第1節 2018.9.20 第一次修正

iOS-KVC官方文件第二部分第1節

Key-Value Coding Fundamatals--Accessing Object Properties

鍵值編碼基礎-- 訪問物件屬性

訪問物件屬性

物件通常在其介面宣告中指定屬性, 這些屬性屬於以下幾種類別之一:

  • 屬性. 指簡單值, 例如標量(scalars)、字串或布林值。值物件(如NSNumber)和其他不可變型別(如NSColor) 也被視為屬性。

  • 一對一關係. 指具有自己屬性的可變物件。物件的屬性可以在物件本身不變的情況下更改。例如,銀行帳戶物件(bank account object)可能具有owner屬性,該屬性是Person物件的例項,該Person物件具有address屬性。owner的address可能會更改,而不會更改銀行帳戶持有的owner。銀行帳戶的owner沒有變更。只有Person的address 發生改變。

  • 一對多關係. 指集合物件。你通常使用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
複製程式碼

為了保持封裝,物件通常為其介面上的屬性提供訪問器方法。物件的作者可以顯式地編寫這些方法,也可以依賴編譯器自動合成它們。無論哪種方式,使用這些訪問器之一的程式碼的作者必須在編譯之前將屬性名稱寫入程式碼中。訪問器方法的名稱成為使用它的程式碼的靜態部分。例如,給定清單2-1中宣告的BankAccount物件,編譯器會合成一個可以為myAccount例項呼叫的setter :

[myAccount setCurrentBalance:@(100.0)];
複製程式碼

這是直接的,但缺乏靈活性。另一方面,符合鍵值編碼的物件提供了使用字串識別符號訪問物件屬性的更通用機制。

使用Keys 和 Key Paths標識物件的屬性

鍵(key)是標識特定屬性的字串。通常, 按照慣例, 表示屬性的鍵(key)是程式碼中顯示的屬性名。鍵(key)必須使用 ASCII 編碼, 不能包含空格, 並且通常以小寫字母開頭 (儘管有例外, 如在許多類中的URL屬性)。

由於清單 2-1中的BankAccount類是符合鍵值編碼的, 所以它能識別鍵(即它的屬性的名稱)ownercurrentBalancetransactions 。您也可以通過其鍵來設定值, 而不是呼叫setCurrentBalance:方法:

[myAccount setValue:@(100.0) forKey:@"currentBalance"];
複製程式碼

實際上, 您可以使用不同的鍵引數通過相同方法來設定myAccount物件的所有屬性。因為引數是字串型別, 所以它可以在執行時操作變數。

鍵路徑Key path是一個用點操作符.來分隔鍵的字串, 用於指定要遍歷的物件屬性序列。序列中第一個鍵的屬性是相對於接收者的, 每個後續鍵相對於上一個屬性的值進行計算。鍵路徑(Key path)對於使用單個方法深入呼叫物件的層次結構很有用。

例如, 應用於銀行帳戶例項的鍵路徑owner.address.street是指儲存在銀行帳戶所有者地址中的街道字串的值, 假設PersonAddress類也符合的鍵值編碼。

注意 在 Swift 中, 您可以使用#keyPath表示式, 而不是使用字串來指示鍵或鍵路徑。這提供了編譯時檢查的優點, 詳見*Using Swift with Cocoa and Objective-C (Swift 4.2) 中的Keys and Key Paths*章節。

使用鍵獲取屬性值

物件在遵循NSKeyValueCoding協議時符合鍵值編碼。繼承自的物件(NSObject提供協議的基本方法的預設實現)會自動採用此協議的某些預設行為。這樣的物件至少實現了以下基於鍵的基本getter:

  • valueForKey: - 返回由key引數指定的屬性的值。如果根據訪問者搜尋模式中描述的規則無法找到key命名的屬性,則該物件會向自身傳送valueForUndefinedKey:訊息。valueForUndefinedKey:引發的預設實現丟擲NSUndefinedKeyException異常,但是子類可以覆蓋此方法並更優雅地處理這種情況。

  • valueForKeyPath: - 返回相對於接收器的指定鍵路徑的值。keyPath序列中的任何物件都不符合特定key的鍵值編碼 - 即,預設實現valueForKey:無法找到訪問器方法 - -那麼就會接收valueForUndefinedKey:訊息。

  • dictionaryWithValuesForKeys: - 返回相對於接收器的一組鍵所對應的值。該方法為陣列中的每個鍵呼叫valueForKey:。返回的NSDictionary包含陣列中所有鍵的值。

注意 集合物件 (如NSArrayNSSetNSDictionary) 不能包含nil的值。而是使用NSNull物件表示nil值。NSNull提供一個表示物件屬性的nil值的單個例項。dictionaryWithValuesForKeys:的預設實現和相關的setValuesForKeysWithDictionary:會在NSNull (在字典引數中) 和nil(在儲存的屬性中)之間進行自動轉換 。

當您使用KeyPath來定址屬性時, 如果鍵路徑中的最後一個鍵是一對多關係 (即引用集合), 則在多對鍵的右側,返回的值是一個包含鍵的所有值的集合。例如, 請求鍵路徑 "transactions.payee" 的值返回包含所有transaction物件中payee物件的陣列。這也適用於KeyPath中的多個陣列。KeyPath accounts.transactions.payee返回一個陣列,其中包含所有帳戶中所有交易的所有收款人物件。

使用鍵設定屬性值

getter一樣, 符合鍵值編碼的物件也提供了一小組通用setter,其預設行為基於以下NSKeyValueCoding協議的實現NSObject:

  • setValue:forKey: - 將指定鍵設定為給定值。setValue:forKey:的預設實現會自動對錶示標量和結構體的NSNumberNSValue物件執行解包操作,並將它們設定到相應的屬性中。有關包裝(warp)和解包(unwarp)語義的詳細資訊,請參閱Representing Non-Object Values

如果接收setter呼叫的物件中沒有對應指定鍵的屬性,該物件將給自己傳送一個[setValue:forUndefinedKey:]訊息。setValue:forUndefinedKey:的預設實現將丟擲NSUndefinedKeyException異常。但是, 子類可以重寫此方法以自定義方式處理請求。

  • setValue:forKeyPath: 在相對於接收者的指定鍵路徑上設定給定值。鍵路徑序列中指定鍵所對應的物件如果不是鍵值編碼相容的,將會收到setValue:forUndefinedKey:訊息。

  • setValuesForKeysWithDictionary: 將指定字典中的值設定到接收者的屬性中, 使用字典鍵標識屬性。預設實現呼叫每個鍵值對的setValue:forKey: , 根據需要用nil替換NSNull物件。

在預設實現中, 當您嘗試將非物件屬性設定為nil值時, 符合鍵值編碼相容物件將自己傳送一個setNilValueForKey:訊息。setNilValueForKey:的預設實現將丟擲[NSInvalidArgumentException]異常, 但物件可以重寫此方法以替換預設值或標記值, 詳見處理非物件值

使用鍵簡化物件訪問

想知道基於key的 gettersetter 如何簡化程式碼, 請檢視下面的示例。在 macOS 中, NSTableViewNSOutlineView物件的識別符號字串與每個它們的列相關聯。如果表的模型物件不是符合鍵值編碼的, 則表的資料來源方法將強制檢查每個列識別符號, 依次查詢要返回的正確屬性, 如清單 2-2所示。此外, 在將來, 當您向模型中新增另一個屬性時, 在本例中為Person 物件, 還必須重新訪問資料來源方法, 新增另一個條件來測試新屬性並返回相關值.

清單 2-2 不基於鍵值編碼的資料來源方法的實現

- (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展示了相同資料來源的方法的一個更緊湊的實現, 該資料來源方法使用的是鍵值編碼相容的Person物件。僅使用valueForKey: getter, 資料來源方法將使用列識別符號作為鍵返回適當的值。除了更短的時間外, 它還更通用, 因為在以後新增新列時, 只要列識別符號始終與模型物件的屬性名稱匹配, 它就會繼續保持不變。

清單 2-3基於鍵值編碼的資料來源方法的實現

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

由於筆者水平有限,文中如果有錯誤的地方,或者有更好的方法,還望大神指出。 附上本文的所有 demo 下載連結,【GitHub】。 如果你看完後覺得對你有所幫助,還望在 GitHub 上點個 star。贈人玫瑰,手有餘香。

相關文章