Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲

KVC編程

編輯:關於Android編程

KVC編程介紹

介紹

本文檔用來描述NSKeyValueCoding非正式接口,NSKeyValueCoding定義了一種允許app通過鍵值來間接訪問一個對象的屬性的機制,而不是直接調用被訪問的對象的getter方法或者是實例對象的值

你可以通過閱讀本文檔理解如何在你的app中使用KVC,並且使之與其他的技術相兼容。KVC是在KVO模式、Cocoa bingdings、Core Data中的一項基本的技術,能夠使你的app可支持applescript語言(AppleScript-able)。在閱讀本文檔前,你應該熟悉基本的Cocoa開發和Objective-C語言

文檔目錄:

什麼是KVC?KVC的概述 常用於描述一個對象的屬性的術語 使用KVC的基本規定 在你的類中應該實現的KVC的存取方法 鍵值驗證 使用KVC必須實現的方法 KVC支持的數據類型 Collection操作和用法 如何確定適當的訪問方法和實例變量 使用元數據定義對象和它們的屬性間的關系 使用KVC的性能考慮

1、什麼是KVC(Key-Value Coding)?KVC的概述

Key-Value Coding(鍵值編碼)是間接存取一個對象的屬性的一種機制,通過使用標識符字符串來定義屬性,而不是直接使用對象的存取方法或者實例對象值。
存取方法,正如名字所展示,提供了你的app的數據模型的屬性的值的存取方法。默認的存取方法有兩個–get方法和set方法。get方法也叫getter,getter將返回一個屬性的值。set方法也叫setter,setter可以設置一個屬性的值。可以重載getter和setter方法來處理對象的屬性及其它關系
在你的app中實現KVC兼容的存取方法非常重要。存取方法有助於數據封裝和集成其他技術,例如KVC、Core Data、Cocoa bindings和腳本。KVC方法在大多數情況下可以簡化你的代碼
KVC的最基本的方法在OC語言的NSKeyValueCoding類的非正式接口中被定義,其默認的實現方法由NSObject提供。
KVC支持帶有值的屬性和標量類型和結構體。非對象參數和返回類型將會被檢測和自動封裝,解包

2、KVC和腳本

在Cocoa中,腳本支持被專門設計用於輕松地實現通過腳本訪問它的模型對象——封裝app的數據的對象。當用戶執行一個applescript指令,結果將直接跳轉到應用模型對象來完成工作
OS X的腳本很大程度上依賴KVC來完成自動完成Applescript指令的執行。在一個使用腳本的app裡面,一個模型對象由一系列的它支持的鍵值定義組成。每個key代表對象的一個屬性。腳本相關的key有文字、字體、文件和顏色。KVC API提供了一種通用和自動的方式來查詢對象key對應的值,並為這些key設置新值。
當你在設計你的app時,你應該為你的對象模型定義一系列的keys並且在適配到他們相應的存取方法中(getter和setter)。然後當你為你的app定義腳本類時,你可以指定這寫keys支持的腳本類。如果你的app支持KVC,那麼你就可以在許多地方隨意地使用腳本了。
在applescript中,在一個app中,對象層定義了模型對象的結構。大多數的appscript指令指定應用中的一個或者多個對象,通過深入到這個對象的父容器中獲取子元素。你可以配置KVC在類描述中定義屬性之間的關系。參見Describing property relationships這篇文章以查看更多細節。
Cocoa腳本支持利用KVC獲取和設置腳本對象的信息。Objective-C非正式協議NSScriptKeyValueCoding 提供了KVC額外功能,包括通過多值key的index獲取和設置鍵值並轉換鍵值到一個合適的數據類型。

使用KVC簡化代碼

你可以在自己的代碼中使用KVC方法。例如,在OS X NSTableView 和NSOutlineView對象都關聯每個列的標示符字符串。通過標示符標示你希望顯示的關鍵屬性,你可以極大的簡化你的代碼
列表1中展示了NSTableView 代理方法不使用KVC的實現。列表2中展示了使用KVC返回相應值,使用列標示符作為key
列表1 不使用KVC實現數據源方法

列表1 不使用KVC實現數據源方法的列表
- (id)tableView:(NSTableView *)tableview
      objectValueForTableColumn:(id)column row:(NSInteger)row {

    ChildObject *child = [childrenArray objectAtIndex:row];
    if ([[column identifier] isEqualToString:@"name"]) {
        return [child name];
    }
    if ([[column identifier] isEqualToString:@"age"]) {
        return [child age];
    }
    if ([[column identifier] isEqualToString:@"favoriteColor"]) {
        return [child favoriteColor];
    }
    // And so on.
}
列表2 數據源使用KVC方法的列表
- (id)tableView:(NSTableView *)tableview
      objectValueForTableColumn:(id)column row:(NSInteger)row {

    ChildObject *child = [childrenArray objectAtIndex:row];
    return [child valueForKey:[column identifier]];
}


術語(Terminology)

KVC定義了一些自己的術語
KVC可以被用於訪問三種不同類型的對象值:屬性、對一關系、對多關系。property指代這任意三種值。
屬性(attribute):屬性是一個簡單的屬性值,比如一個標量、字符串或者是一個bool值。值對象例如NSNumber和其他不可變類型比如NSColor同樣也術語屬性。
對一關系(to-one relationship):一個對象擁有多個屬性,那麼它就是一種多對一的關系。這些屬性的改變並不會引起對象本身的改變,比如一個NSView的父視圖就是一種對一關系。
對多關系(to-many relationship):通常是一個包含關系對象的Collection。NSArray 或NSSet 的實例通常用於這種集合。然而,通過實現中多重屬性模式的訪問器集合(Collection Accessor Patternsfor To-Many Properties)的KVC訪問器,KVC允許你使用定制集合類並訪問他們,仿佛他們是NSArray 或NSSet 。



KVC的基本原理

這個模塊是講述KVC的基本原理。

keys和key路徑

一個key是一個對象的一個某一個屬性的一個標識符字符串。通常來說,一個key對應了一個存取方法或者接受對象的實例變量。key必須使用ASCII編碼,以小寫字母開頭,並且不包含空格。
一些keys的例子:payee,openingBalance,transactions或者amount
Key路徑是是點分割key的字符串,用於指定對象屬性遍歷序列。第一個key的屬性與接收者相關,每個後續關鍵key與前一個屬性的值相關。
例如,key路徑address.street可以從接收對象獲取地址屬性的值,並確定街道屬性與地址對象相關。

使用KVC獲取屬性值

valueForKey:方法返回接收者相關指定key的值。如果沒有指定key的訪問器或實例變量,接收者發送自身valueForUndefinedKey:消息。 valueForUndefinedKey:默認實現有一個 NSUndefinedKeyException子類可以重寫該行為。
valueForKeyPath:返回相關接收者指定key路徑的值。Key路徑序列中的任何對象中不兼容KVC屬性key將接收到 valueForUndefinedKey:消息。
dictionaryWithValuesForKeys: 方法搜索接收者相關key數組值。返回的NSDictionary包含數組中的所有key的值
注意:集合對象,例如NSArray, NSSet和NSDictionary ,不能包含nil作為值。相反,你應該使用特定對象NSNull來代替nil。NSNull提供了實例代表對象屬性值為nil。dictionaryWithValuesForKeys:和setValuesForKeysWithDictionary:的默認實現自動轉換NSNull和nil,所以你的對象不需要顯式的測試NSNull值。
當key路徑返回的值包含很多屬性的key,並且該key不是路徑中最後的key,返回值是一個集合,包含所有key對應的值。例如,請求key路徑transactions.payee 返回一個數組包含所有交易的所有payee對象。這對key路徑中多數組通用使用。Key路徑accounts.transactions.payee返回所有賬戶所有交易的所有payee對象。

使用KVC設置屬性值

setValue:forKey:方法設置接收者相關特定key的值。setValue:forKey:的默認實現自動將NSValue對象拆包為標量和結構來分配他們的屬性。參見標量結構幫助( Scalar and Structure Support)了解更多關於裝包和拆包語法。
如果指定的key不存在,接收者發送setValue:forUndefinedKey:一個消息。setValue:forUndefinedKey:的默認實現有NSUndefinedKeyException,然而子類可以重寫此方法來自定義的方式處理請求。
setValue:forKeyPath: 方法類似的方式實現,但它能夠處理key路徑。
最後,setValuesForKeysWithDictionary:設置接收者的屬性值為指定字典,使用字典的key來識別屬性。setValue:forKey: 默認實現,對每個鍵值對,按要求使用NSNull代替nil。
你應該考慮一個額外的問題,當試圖設置一個非空屬性為nil值時會發生什麼。在這種情況下,接收者會發送setNilValueForKey: 消息。setNilValueForKey: 默認實現有NSInvalidArgumentException。你的應用可以覆蓋整個方法來替代默認值或標示值,然後調用setValue:forKey:設置新值。

點語法和KVC

在OC中,點語法和KVC是相互獨立的,無論你是否使用點語法,你都可以使用KVC,同樣,無論你是否使用KVC你都可以使用點語法。在KVC中,點語法是用來劃分key路徑中的元素。當然,當你使用點語法訪問一個屬性時,你調用的的是接收者的標准訪問器方法。
你可以使用KVC方法去訪問一個屬性,比如,對於給定的一個定義的類如下:

@interface MyClass
@property NSString *stringProperty;
@property NSInteger integerProperty;
@property MyClass *linkedInstance;
@end

你可以這樣在一個實例中訪問它的屬性:

MyClass *myInstance = [[MyClass alloc] init];
NSString *string = [myInstance valueForKey:@"stringProperty"];
[myInstance setValue:@2 forKey:@"integerProperty"];

下面的代碼展示點語法和KVC的key路徑的不同

//這個是點語法
MyClass *anotherInstance = [[MyClass alloc] init];
myInstance.linkedInstance = anotherInstance;
myInstance.linkedInstance.integerProperty = 2;
//這個是Key path
MyClass *anotherInstance = [[MyClass alloc] init];
myInstance.linkedInstance = anotherInstance;
[myInstance setValue:@2 forKeyPath:@"linkedInstance.integerProperty"];


KVC訪問器方法

為了使KVC能夠正確地調用 valueForKey:, setValue:ForKey:, mutableArrayValueForKey:, mutableSetValueForKey:,你應該實現KVC的存取方法。

備注:在本節中,訪問器模式會寫成-set< key>:或者-< key>的格式。< key>表示你的屬性的key的名字。你在實現自己的相應的方法時,應該把這些< key>或者< key>代替成確切的key。比如,在名字屬性中,-set< key>:應該寫成-setName:,-< key>應該寫成-name。
常用的訪問器模式

一個訪問器方法的格式:返回一個屬性是-< key>。-< key>方法將返回一個對象,標量或者一個數據結構體。備用的命名格式-is< key>支持Boolean類型的屬性。
列表1展示了使用常用的命名hidden屬性的方法,列表2展示了使用備用命名格式的方法。

//列表1 為一個hidden屬性訪問器命名常用格式
- (BOOL)hidden {
   // Implementation specific code.
   return ...;
}
//列表2 為一個hidden屬性備用的訪問器命名格式
- (BOOL)isHidden {
   // Implementation specific code.
   return ...;
}

為了使一個屬性或者對一關系支持setValue:forKey,你必須實現一個set< key>格式的訪問器。下面列表3是實現hidden屬性的訪問器方法:

//列表3 支持hidden屬性的訪問器命名格式
- (void)setHidden:(BOOL)flag {
   // Implementation specific code.
   return;
}

如果屬性是一個非對象類型,你也必須實現一個合適的方法來表示一個空的值。當你想設置一個屬性為空的時候,KVC中的setNilValueForKey:方法將被調用。這個方法將會在你的app中,在類中提供一些合適的默認值或者是沒有相應的訪問器的handle keys。
接下來的例子當hidden將被設置成為nil的時候,hidden會被設置為YES。它創建了一個NSNumber實例,包括了一個boolean值,然後使用setValue:forKey:來設置一個新的值。這將維持這個模型的封裝,確保所有額外的應該改變這個值的行為都將發揮作用。這個比起直接調用訪問器方法或者直接設置實例變量,被認為是更好的做法。

- (void)setNilValueForKey:(NSString *)theKey {

    if ([theKey isEqualToString:@"hidden"]) {
        [self setValue:@YES forKey:@"hidden"];
    }
    else {
        [super setNilValueForKey:theKey];
    }
}
對多屬性的Collection的訪問器模式

盡管你的app可以通過使用-< key>和-set< key>的訪問器格式來實現對多關系屬性,你通常應該只使用這些方法來創建集合對象。用於操作這個集合的呢哦榮的最佳方法應該是實現一個額外的訪問器方法,我們稱之為Collection訪問器方法。你可以使用集合訪問器方法,或者是一個由mutableArrayValueForkey:或者mutablesetvalueforkey方法返回的可變的Collection代理。
實現Collection訪問器方法,比起最基本的getter,有下面許多優點:

當存在多對可變的對多關系時,性能將得到提高 對多關系可以通過實現相應的訪問器Collection用NSArray或者NSSet建模,實現集合訪問器方法可以在使用KVC方法時,區分數組或者集合

你可以直接使用Collection訪問器方法來對KVO模式的Collection進行直接的修改。查看 KVO編程指南了解更多

Collection訪問器方法有兩個變種:為已經排序的對多關系准備的索引訪問器和未排序並且不需要排序的關系的無序訪問器。

索引訪問器模式

在一個排序好的關系中,索引訪問器方法定義了一種對象計數、恢復、增加和代替的機制。通常這個關系是一個NSArray或者是NSMutableArray的實例。任何對象可以像一個數組一樣,實現這些方法和被操作。你不僅僅是簡單地實現這些方法,你也可以直接在關系中調用這些對象。
從Collection或者可變訪問器返回數據的索引訪問器提供了一個接口方法mutableArrayValueForKey:用來修改集合。

獲取索引訪問器

為了對多關系的支持只讀,你應該實現下面的方法:

-countOf< key>.(必須實現)這個是一個類似NSArray的原始方法 count -objectIn< key>Atindex: 或者 < key>AtIndexes .其中一個必須實現,它們的作用就跟NSArray的方法 objectAtIndex 和 objectsAtIndexes:一樣 -get< key>:range:.這個方法是可選的,實現之後可以提高性能。這個方法跟NSArray的方法 getObject:range:一樣。

-countOf< key>方法的實現將簡單地返回一個NSUInteger類型的數據用來表示在對多關系裡面的對象的數量。列表4的代碼片段說明了-countOf< key>實現了對多關系中employees屬性的具體實現。

//-count< key>實現例子
- (NSUInteger)countOfEmployees {
    return [self.employees count];
}

方法-objectIn< key>AtIndex:返回在一個對多關系中的一個確切索引指向的對象。方法-< key>AtIndexes: 訪問器將返回以格式通過NSIndexSet格式的索引確切地返回一個對象數組。上面兩個方法只能有一個被實現。
列表5的代碼片段實現了-objectIn< key>AtIndex:和-< key>AtIndexes:在對多關系的employees屬性中的實現。

//列表5 -objectIn< key>AtIndex: 和-< key>AtIndexes:的實現

- (id)objectInEmployeesAtIndex:(NSUInteger)index {
    return [employees objectAtIndex:index];
}

- (NSArray *)employeesAtIndexes:(NSIndexSet *)indexes {
    return [self.employees objectsAtIndexes:indexes];
}

如果標准流程指明性能改善是必須的,那麼你可以實現-get< key>:range:。你的訪問器的實現應該在緩沖區給定的兩個參數范圍中返回對象。(其實就是一個指定了參數范圍的返回方法)。
列表6展示了對多關系中的employees屬性的-get< key>:range: 的實現

//-get< key>:range: 方法的例子
- (void)getEmployees:(Employee * __unsafe_unretained *)buffer range:(NSRange)inRange {
    // Return the objects in the specified range in the provided buffer.
    // For example, if the employees were stored in an underlying NSArray
    [self.employees getObjects:buffer range:inRange];
}
可變索引訪問器

索引訪問器要支持一個可變的對多關系需要額外實現一些方法。實現可變索引訪問器將允許你的app通過使用mutableArrayValueForKey:方法返回的數組代理更加便捷而有效地操作索引Collection。另外,通過實現這些方法,你的類的對多關系的屬性將會遵守KVO.

注意:強烈建議你實現這些可變訪問器而不是通過一個訪問器直接返回一個可變的Collection。這些可變訪問器在關系發生數據變化的時候將更加的有效率。

為了使一個有序的對多關系遵守KVO,你應該實現下列的方法:

-insertObject:in< key>AtIndex:或者-insert< key>:atIndexes:.至少有其中一個方法必須實現,這個就類似於NSMutableArray方法裡面的insertObject:atIndex和insertObjects:atIndexes:。 removeObjectFrome< key>AtIndex:或者-remove< key>AtIndexes.至少有其中一個方法必須實現,這些方法分別類似於NSMutableArray裡面的 removeObjectAtIndex: 和 removeObjectsAtIndexes: -replaceObjectIn< key>AtIndex:withObject: 或者 -replace< key>AtIndexes:with< key>:。可選的,當主流程對性能要求比較高時實現它。

-insertObject:in< key>AtIndex: 方法參數傳入一個要插入的對象,一個表示索引位置的NSUInteger類型的index。-insert< key>:atIndexes:方法通過一組索引NSIndexSet類型的數組往Collection中插入一個對象數組。你只需要實現這兩個方法的其中一個。
列表7展示了對多關系的employee屬性的插入訪問器的實現

//對多關系的employee屬性的插入訪問器的實現
- (void)insertObject:(Employee *)employee inEmployeesAtIndex:(NSUInteger)index {
    [self.employees insertObject:employee atIndex:index];
    return;
}

- (void)insertEmployees:(NSArray *)employeeArray atIndexes:(NSIndexSet *)indexes {
    [self.employees insertObjects:employeeArray atIndexes:indexes];
    return;
}

如果程序對性能要求比較高時,你也可以實現下面兩個可選的代替訪問器的其中一個,你實現-replaceObjectIn< key>AtIndex:withObject: 或者 -replace< key>AtIndexes:with< key>:,當一個對象在Collection中被代替時,這個方法會被調用,這個方法將直接替換對象,而不是先刪除,在插入。
列表9展示了-replaceObjectIn< key>AtIndex:withObject: 和 -replace< key>AtIndexes:with< key>:在對多關系中employee屬性的實現

- (void)replaceObjectInEmployeesAtIndex:(NSUInteger)index
                             withObject:(id)anObject {

    [self.employees replaceObjectAtIndex:index withObject:anObject];
}

- (void)replaceEmployeesAtIndexes:(NSIndexSet *)indexes
                    withEmployees:(NSArray *)employeeArray {

    [self.employees replaceObjectsAtIndexes:indexes withObjects:employeeArray];
}
無序訪問器模式

無序訪問器方法提供了一個訪問和改變一個無序關系中的對象的方法。一般來說,這個關系應該是一個NSSet或者是一個NSMutableSet的實例對象。無論如何,通過實現這些訪問器,任何類都可以像使用一個NSSet實例對象一樣使用,可以被用於模型化關系和使用KVC。

獲取無序訪問器

無序訪問器模式實現之後就可以把這個對多關系看成是一個NSSet的實例對象,比如-countOf< key>方法類似於 NSSet的 count方法; -enumeratorOf< key>類似於 NSSet的 objectEnumberator方法;-memberOf< key>相當於member方法。

- (NSUInteger)countOfTransactions {
    return [self.transactions count];
}

- (NSEnumerator *)enumeratorOfTransactions {
    return [self.transactions objectEnumerator];
}

- (Transaction *)memberOfTransactions:(Transaction *)anObject {
    return [self.transactions member:anObject];
}
- (void)addTransactionsObject:(Transaction *)anObject {
    [self.transactions addObject:anObject];
}

- (void)addTransactions:(NSSet *)manyObjects {
    [self.transactions unionSet:manyObjects];
}
- (void)removeTransactionsObject:(Transaction *)anObject {
    [self.transactions removeObject:anObject];
}

- (void)removeTransactions:(NSSet *)manyObjects {
    [self.transactions minusSet:manyObjects];
}
- (void)intersectTransactions:(NSSet *)otherObjects {
    return [self.transactions intersectSet:otherObjects];
}

鍵值校驗

KVC提供了一系列的API來校驗一個屬性值。校驗基礎結構提供一個類接受一個值的機會,結果將返回另一個值,或者拒絕一個屬性的新值並返回錯誤的理由。

校驗方法命名規則

就好像訪問器方法的命名規則一樣,關於屬性的校驗方法也有命名規則。一個校驗方法的規則應該是validata< key>:error:。列表1展示了name屬性的校驗方法的命名規則:

-(BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError {
    // Implementation specific code.
    return ...;
}
實現一個校驗方法

校驗方法通過引用傳遞連個參數:要校驗的值對象和用於返回錯誤信息的NSError對象。
一個校驗方法可能有三種輸出:

1.值對象是有效的,不改變值對象和錯誤信息,並且返回YES

2.值對象是無效的,並且一個有效的值不能被創建和返回。結果在把錯誤設置到NSError參數中並且返回NO以表示校驗的結果是失敗。

3.一個新的有效的值對象被創建和返回。這種情況下,將參數設置成一個新的有效值,然後返回YES。返回的錯誤是不變的。你必須返回一個新的對象,而不是僅僅修改傳遞的ioValue,即使它是可變的。

-(BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError{

    // The name must not be nil, and must be at least two characters long.
    if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 2)) {
        if (outError != NULL) {
            NSString *errorString = NSLocalizedString(
                    @"A Person's name must be at least two characters long",
                    @"validation: Person, too short name error");
            NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorString };
            *outError = [[NSError alloc] initWithDomain:PERSON_ERROR_DOMAIN
                                                    code:PERSON_INVALID_NAME_CODE
                                                userInfo:userInfoDict];
        }
        return NO;
    }
    return YES;
}
重要:一個返回NO的校驗方法必須先檢查outError參數是否為空。如果outError參數不是空的,那麼校驗方法應該設置outError參數為一個有效的NSError對象。
調用校驗方法

你可以直接調用校驗方法,也可以通過調用 -validataValue:forKey:error和一個確切的key來調用校驗方法。默認的實現方法 -validateValue:forKey:error 在類中搜索那些名字和validata< key>:error相匹配的receiver。如果確實有這樣的一個方法,那麼它將會被引用和作為結果返回 。如果沒有這樣的方法,validataValue:forkey:error返回YES,然後校驗這個值。

警告:當你為一個屬性實現方法-set< key>時,不應該調用校驗方法
自動校驗

通常來說,KVC不會自動校驗,引用校驗方法是你的app的責任
有些技術在某些環境下回自動地進行校驗:Core Data當對象內容被保存時會自動地執行校驗方法。在OS X中,Cocoa綁定允許你指定自動進行校驗的方法。(間Cocoa綁定編程指南以查看更多)

標量的校驗

校驗方法要求參數值是一個對象,作為結果值返回的非對象屬性值會被包裝成NSValue或者NSNumber類,參見標量和結構體支持文。下面是例子。

-(BOOL)validateAge:(id *)ioValue error:(NSError * __autoreleasing *)outError {

    if (*ioValue == nil) {
        // Trap this in setNilValueForKey.
        // An alternative might be to create new NSNumber with value 0 here.
        return YES;
    }
    if ([*ioValue floatValue] <= 0.0) {
        if (outError != NULL) {
            NSString *errorString = NSLocalizedStringFromTable(
                @"Age must be greater than zero", @"Person",
                @"validation: zero age error");
            NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorString };
            NSError *error = [[NSError alloc] initWithDomain:PERSON_ERROR_DOMAIN
                code:PERSON_INVALID_AGE_CODE
                userInfo:userInfoDict];
            *outError = error;
        }
        return NO;
    }
    else {
        return YES;
    }
    // ...


確保KVC遵從

為了使你的類的特定屬性符合KVC規則,你必須為這些屬性實現方法valueForKey:和setValue:forKey:。

屬性和對一關系的遵從

如果一些屬性是attribute或者對一關系,則下面這些方法必須實現:

實現以 -< Key>, -is< key>或者是 < key>或者 _< key>命名的實例對象和方法.盡管key頻繁地以一個小寫字母開頭,KVC也支持以一個大寫字母開頭,比如URL。 如果一個屬性是可變的,那麼你也應該實現方法-set< key>:。 當你實現-set< key>時不能調用校驗方法。 如果需要校驗方法是你的類應該實現方法 -validate< key>:error:。
索引對多關系的遵從

在索引對多關系中,KVC要求你實現下面的方法:

實現以-< key>命名並且返回一個數組的方法或者有數組實例以< key>或者_< key>命名。 實現方法-countOf< key>和 -objectIn< key>AtIndex:或者-< key>AtIndexes:的其中一個。 為了提高性能,你可以實現-get< key>:range:方法。

對於一個可變的排序的索引對多關系,KVC要求你實現下面的方法:

-insertObject:in< Key>AtIndex: 或者 -insert< Key>:atIndexes:其中一個 -removeObjectFrom< Key>AtIndex: or -remove< Key>AtIndexes:其中一個 -replaceObjectIn< Key>AtIndex:withObject: 或者 -replace< Key>AtIndexes:with< Key>: 來提高性能
無序對多關系的遵從

在無序對多關系中,KVC要求你實現下面的方法:

實現以-< key>命名並且返回一個數組的方法或者有數組實例以< key>或者_< key>命名。 實現方法-countOf< key>和 -enumeratorOf< Key>, 和 -memberOf< Key>:。

對於一個可變的無序對多關系,KVC要求你實現下面的方法:

-add< Key>Object: 或者 -add< Key>:其中一個 -remove< Key>Object: or -remove< Key>:其中一個 -intersect< Key>: and -set< Key>: 你可以選擇實現這兩個方法來提高性能


標量和結構體支持

KVC通過自動地把標量值和數據結構體封裝和解包成NSNumber和NSValue類型的實例數據來實現對標量和結構體的支持。

描述非對象值

默認的實現方法valueForkey:和setValue:forkey:提供了對把非對象數據類型,包括標量和結構體封裝成對象的支持。
一旦valueForKey:方法在確切的訪問器方法或者實例對象值中被定義,它檢查返回類型或者數據類型,如果只的返回類型不是一個對象,就創建一個NSNumber或者NSValue對象來代替這些非對象數據類型。
同樣的,setValue:forKey:一檔在訪問器方法或者實例對象中被定義,如果數據類型不是一個對象,那麼程序將會調用從-< type>value方法中返回的對象作為返回對象。

處理nil值

當setValue:forkey:傳入一個空值作為非對象屬性的參數時,一個額外的問題會出現,並且它沒有相應的處理方法。當非對象屬性的值為空時,應該調用setNilValueForKey:方法。默認的setNilValueForKey方法會拋出一個NSInvalidArgumentException異常。一個子類可以重載這個方法來處理相應的行為。例子參見下面代碼

- (void)setNilValueForKey:(NSString *)theKey
{
    if ([theKey isEqualToString:@"age"]) {
        [self setValue:[NSNumber numberWithFloat:0.0] forKey:@”age”];
    } else
        [super setNilValueForKey:theKey];
}
封裝和解包標量類型
Data type Creation method Accessor method BOOL numberWithBool: boolValue 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
封裝和解包結構體
Data type Creation method Accessor method NSPoint valueWithPoint: pointValue NSRange valueWithRange: rangeValue NSRect valueWithRect: (OS X only). rectValue NSSize valueWithSize: sizeValue

自動封裝和解包不限制與NSPoint,NSRange,NSRect和NSSize結構體類型。例如,你可以像下下面這樣定義一個類:

typedef struct {
    float x, y, z;
} ThreeFloats;

@interface MyClass
- (void)setThreeFloats:(ThreeFloats)threeFloats;
- (ThreeFloats)threeFloats;
@end

-mark

通過使用參數@”threeFloats”調用valueForkey方法發送MyClass的實例對象的消息將會調用MyClass方法threeFloats,然後結果將包裝成為一個NSValue類型。除此之外向用一個ThreeFloats結構體包裝成的NSValue對象向一個MyClass的實例對象發送一個setValue:forKey:的消息,將會調用setThreeFloats方法,然後結果將會得到getValue:message的結果。

Collection操作
例子中的數據使用
簡單的Collection操作
數組和集合操作
訪問器搜索的實現細節
簡單屬性的訪問器搜索模式
有序collection的訪問器搜索模式
有序非重復collection的訪問器搜索模式
無序collection的訪問器搜索模式

—–以上坑待填


描述屬性關系

在一個類中,類描述提供了一種方法去描述對一和對多關系、 在使用KVC後,定義這些類屬性間的關系允許對這些屬性進行更智能和靈活的操作。

類描述

NSClassDescription是一個提供了獲取類的元數據接口的基礎類。一個類描述對象在一個個類的對象和其他對象間,記錄了一個特定的類的對象的可獲取屬性和關系(一對一,一對多和互斥)。例如屬性方法將返回一個類定義的所有屬性。方法toManyRelationshipKeys和toOneRelationshipKey:返回定義了對多和對一關系的keys的數組,inverseRelationshipKey:根據提供的keys返回從關系的終點指向回的關系的名字。
NSClassDescription 不定義用於定義關系的方法,具體的方法由子類來實現。一旦創建成功,你就可以通過NSClassDescription裡的 registerClassDescription:forClass:類方法來注冊你的類。
NSScriptClassDescription 是Cocoa裡提供的唯一的NSClassDescription的實例子類。它封裝了一個app的腳本的信息。



性能考慮

盡管KVC是非常有效率的,但是它添加了一個比直接調用的方法級別更加低的間接尋址方法。所以只有當你能夠通過它將程序變得更加靈活的時候你才應該使用它。可能在將來,我們會添加額外的優化,但是這些優化並不會改變KVC遵循最基本的方法。

重載KVC方法

默認的時間KVC的方法,比如valueForKey: 緩存OC運行信息來增加效率。當你重載這些方法的時候,你應該注意確保你的重載方法不會影響app的性能。

優化對多關系

在使用索引格式的訪問器中的對多關系實現將會造成比較多的性能消耗。
建議你盡可能地在對多關系集合中使用最少的索引訪問器。參見Collection Accessor Patterns for To-Many Properties以查看更多


Introduction to Key-Value Observeing Programming Guide

關於KVO模式的介紹

Key-value observing (鍵值觀察模式)是一個對象的值發生變化時,將得到注意、通知的一個機制

在了解KVO之前,必須先學習Key-value coding,主要內容在上面。

一言蔽之

KVO提供了一種當某些確切的屬性發生變化的時候,系統將得到通知的機制。在一個app中,這樣的機制在模型和布局控制器發生交流的時候就顯得十分的有用。(在OS X中,布局控制器綁定技術非常依賴KVO。)一個控制器對象一般觀察模型對象的屬性值,一個視圖對象通過一個控制器來觀察模型對象的屬性值。另外,一個模型對象可能會觀察其他模型對象(通常是決定當一個依賴的值發生改變時,它是否要隨之改變)或者甚至它自己(當一個依賴的值發生改變它是否改變)。
你可以觀察的屬性值應該包括簡單的屬性,對一關系和對多關系。對多關系的觀察者將會被告知改變的類型和變化中涉及到改變的對象。
要設置一個屬性的觀察者有三個步驟,明白這三個步驟就可以很清楚地說明KVO是怎麼樣工作的。

1.首先,看你鍵值觀察的對象是否有直接的相關,比如,當有一個對象的一個特定的屬性發生了任何改變時,與之相關的對象應該得到通知。

例如,一個personobject類應該注意任何他們在bankobject類中的accountbalance屬性發生的任何改變

2.personobject類必須通過發送一個 addObserver:forKeyPath:options:context:消息來注冊成為一個bankobject類的accountbalance屬性的觀察者

注意:方法 addObserver:forKeyPath:options:context: 建立了一個你確定的實例對象之間的鏈接。兩個類之間是不能建立連接的,但是兩個實例對象可以。

3.為了相應改變通知,觀察者必須實現observerValueForKeyPath:ofObject:change:context:方法。這個方法的實現定義了觀察者怎樣相應這些改變通知。在這個方法裡面你可以定制對其中一個改變的屬性的相應行為。
Registering for Key-Value Observing描述了怎麼樣注冊和接受觀察通知。

4.方法 observeValueForKeyPath:ofObject:change:context:當觀察的屬性值以KVO兼容的方式改變時,或者它指向的key發生改變時,這個方法就會自動調用。

Registering Dependent Keys確定一個值的key取決於另一個key的值

KVO的最基本的有點就是每次當一個屬性發生變化時,你不需要實現發送通知的流程。它已經定義好的基層有著框架級別的支持,可以讓更加容易地實現。一般來說你不需要在你的工程中添加額外的代碼。另外,這個基層已經十分的完善了,可以支持對一個單一屬性或者以來至添加多個觀察者。

KVO Compliance 描述了自動和手動鍵值觀察的不同,和如何同時實現

不像通知中心模式使用NSNotificationCenter,對於所有的觀察者並沒有中央對象來提供改變通知。通知將直接從發生改變的對象發送的觀察者對象。NSObject實現了最基礎的KVO,你基本不需要去重載這些方法。

Key-Value Observing Implementation Details描述了KVO模式是怎麼樣實現的。


KVC實現

為了使一個特定的屬性遵從KVO,這個類必須確保下面幾點:

這個類的屬性必須是KVC實現的 這個類為屬性發出KVO改變通知 已經注冊過依賴key(詳見Registering Dependent Keys)

有兩個技術確保了改變通知的發出。由NSObject提供的自動支持和一個類中所有屬性默認可用的KVC實現。一般來說,你遵守標准的Cocoa編碼和命名規范,你就可以使用自動改變通知,你並不需要寫額外的代碼。
手動改變通知當通知發出時提供了額外的控制,並且需要額外的編碼。你可以通過實現類方法automaticallyNotifiesObserversForKey:方法在你的子類的屬性控制自動通知。

自動改變的通知

NSObject提供了一個基本的自動鍵值改變通知的實現。自動鍵值改變通知告知觀察者那些鍵值實現和KVC方法的訪問器所作出的改變。自動通知也通過collection代理對象返回被支持,比如mutaleArrayValueForKey:。參照下面代碼:

// Call the accessor method.
[account setName:@"Savings"];

// Use setValue:forKey:.
[account setValue:@"Savings" forKey:@"name"];

// Use a key path, where 'account' is a kvc-compliant property of 'document'.
[document setValue:@"Savings" forKeyPath:@"account.name"];

// Use mutableArrayValueForKey: to retrieve a relationship proxy object.
Transaction *newTransaction = <#Create a new transaction for the account#>;
NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];
[transactions addObject:newTransaction];
手動改變的通知

手動改變通知通過怎麼樣的和什麼時候的通知被發送到觀察者提供了更加細致的控制。這可以幫助盡可能少地觸發一些不需要的通知,或者把一組改變變成一個單一的通知。
一個要實現手動通知的類必須重載NSObject類中的automaticallyNotifiesObserversForKeys:方法。在一個類中同時使用自動和手動的通知也是有可能的。屬性執行手動通知,子類實現automaticallyNotifiesObserversForKey:應該返回NO,一個子類的實現應該調用父類包括父類中的所有未識別的keys(意思就是這個子類中沒有這個key時,應該在父類中找是否有這個key?)。參照下面代碼:

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {

    BOOL automatic = NO;
    if ([theKey isEqualToString:@"openingBalance"]) {
        automatic = NO;
    }
    else {
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}

為了實現手動觀察者通知,你應該在改變值前調用willChangeValueForKey:,在改變值後調用didChangeValueForKey:。參見下面代碼:

- (void)setOpeningBalance:(double)theBalance {
    [self willChangeValueForKey:@"openingBalance"];
    _openingBalance = theBalance;
    [self didChangeValueForKey:@"openingBalance"];
}

你可以通過先檢查值是否發生改變來盡可能地減少發送一些不必要的通知。參見下面代碼:

- (void)setOpeningBalance:(double)theBalance {
    if (theBalance != _openingBalance) {
        [self willChangeValueForKey:@"openingBalance"];
        _openingBalance = theBalance;
        [self didChangeValueForKey:@"openingBalance"];
    }
}

當一個簡單的改變會引起多個keys發生改變時,你應該把這些改變通知合並。

- (void)setOpeningBalance:(double)theBalance {
    [self willChangeValueForKey:@"openingBalance"];
    [self willChangeValueForKey:@"itemChanged"];
    _openingBalance = theBalance;
    _itemChanged = _itemChanged+1;
    [self didChangeValueForKey:@"itemChanged"];
    [self didChangeValueForKey:@"openingBalance"];
}

涉及到有序對多關系時,你應該考慮的不僅是key的改變,還應該包括索引的對象的改變那和類型的改變。類型的改變是一個包含了 NSKeyValueChangeInsertion, NSKeyValueChangeRemoval,或者NSKeyValueChangeReplacement的NSKeyValueChange對象,索引對象則是傳遞一個NSIndexSet對象。

- (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes {
    [self willChange:NSKeyValueChangeRemoval
        valuesAtIndexes:indexes forKey:@"transactions"];

    // Remove the transaction objects at the specified indexes.

    [self didChange:NSKeyValueChangeRemoval
        valuesAtIndexes:indexes forKey:@"transactions"];
}
注冊依賴keys

一個屬性的值由另一個對象的一個或者多個屬性決定有許多情況。如果一個屬性的值改變,這個值的派生屬性應該也是被標記被改變。你如何確保這些依賴項屬性值監聽通知張貼取決於關系的基數。

對一關系

為了在對一關系中自動觸發通知你應該重載keyPathForValueAffectingValueForKey:或者定義一個注冊為依賴值的適合的方法模式。

- (NSString *)fullName {
    return [NSString stringWithFormat:@"%@ %@",firstName, lastName];
}

一個觀察fullname屬性的app應該在firstname或者lastname屬性改變或者他們屬性的值發生改變的時候被通知。
一種解決方案就是重載keyPathForValueAffectingValueForKey:指定fullname屬性是基於lastname和firstname屬性的一個人的屬性。

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {

    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];

    if ([key isEqualToString:@"fullName"]) {
        NSArray *affectingKeys = @[@"lastName", @"firstName"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}

你的重載方法一般應該調用父類並且放回一個包括了所有成員的父類。
你也可以通過實現一個叫做keyPathsForValuesAffecting< key>名字的類方法來達到這個目的。

+ (NSSet *)keyPathsForValuesAffectingFullName {
    return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}

當你往一個已經存在的使用category的類中添加一個計算出來的屬性時,你不能重載keyPathsForValueAffectingValueForKey方法,因為你不應該在categories裡重載方法。在這個例子中,實現keyPathsForValuesAffecting< key>類方法更有優勢。

注意:你不能通過實現keyPathsForValuesAffectingValueForKey:方法在對多關系中設置依賴值。你應該觀察適當的對多關系集合中的每個對象中的適當的屬性,然後根據這些值的改變更新他們的key。接下來這一章展示了這種情況的處理方法。
對多關系

keyPathsForValuesAffectingValueForKey:方法不支持對多關系的key-paths。例如,你對於一個employee類有一個有對多關系的Department對象,並且employee有一個工資屬性。那麼你應該想department對象有一個totalsalary屬性,可以把所有的職員的工資自動加起來。那麼你就不能,比如使用keyPathForValuesAffectingTotalSalary和把employees.salary作為key返回。
兩面有兩種解決這種情況的方法:

1.你可以使用KVO注冊父類為一個他們所有子類的相關屬性的觀察者。你應該隨著子類的添加和刪除添加和刪除父類的觀察者。在observeValueForKeyPath:ofObject:change:context:方法中更新你的依賴值,下面代碼是示例:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if (context == totalSalaryContext) {
        [self updateTotalSalary];
    }
    else
    // deal with other observations and/or invoke super...
}

- (void)updateTotalSalary {
    [self setTotalSalary:[self valueForKeyPath:@"[email protected]"]];
}

- (void)setTotalSalary:(NSNumber *)newTotalSalary {

    if (totalSalary != newTotalSalary) {
        [self willChangeValueForKey:@"totalSalary"];
        _totalSalary = newTotalSalary;
        [self didChangeValueForKey:@"totalSalary"];
    }
}

- (NSNumber *)totalSalary {
    return _totalSalary;
}

如果你使用Core Data,你應該在app通知中心中把父類注冊為它管理的對象內容觀察者。當子類發生變化時,父類應該像鍵值觀察模式一樣該得到相關的通知。

KVO的實現細節

自動鍵值觀察是通過一種叫isa-swizzling技術實現的。

isa指針,正如它的名字所示,指向了一個維護調度表的一個類。這個調度表本質上包括了類實現的方法和其他數據的所有指針。

當一個觀察者為一個對象的一個屬性被注冊時,觀察對象的isa指針被修改,指向一個中間類而不是原來的那個真實的類。所以isa指針的值不一定指向實例對象的那個類。你不能通過isa指針來決定類成員,你應該使用實例對象的類方法來決定類。


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved