ios--kvc/kvo使用詳解

這兩天在看和這個相關的的內容,全部推翻重寫一個版本,這是公司內做技術分享的文檔總結,對結構、條理做了更清晰的調整。先找了段代碼,理解下,網上看到最多的一段的關於KVC的代碼

先上代碼 

1.     .Person 

2.     @implementation Person 

3.     @synthesize name,age;//屬性name 將被監視 

4.     -(void) changeName 

5.     

6.         name=@"changeName directly"; 

7.     

8.     @end 

9.      

10.   

11.  2.PersonMonitor  監視了name屬性 

12.  @implementation PersonMonitor 

13.   

14.  (void)observeValueForKeyPath:(NSString *)keyPath 

15.                        ofObject:(id)object 

16.                          change:(NSDictionary *)change 

17.                         context:(void *)context 

18.  

19.      if ([keyPath isEqual:@"name"]) 

20.      

21.          NSLog(@"change happen, old:%@   new:%@",[change objectForKey:NSKeyValueChangeOldKey],[change objectForKey:NSKeyValueChangeNewKey]); 

22.      

23.  

24.  @end 

25.   

26.   

27.  3測試代碼 

28.   

29.     //初始化被監視對象 

30.      Person *p =[[Person alloc] init]; 

31.     //監視對象 

32.     PersonMonitor *pm= [[PersonMonitor alloc]init]; 

33.      [p addObserver:pm forKeyPath:@"name" options:(NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld) context:nil]; 

34.     

35.  //測試前的數據 

36.      NSLog(@"p.name is %@",p.name); 

37.      

38.  //通過setvalue 的方法,PersonMonitor的監視將被調用 

39.    [p setValue:@"name kvc" forKey:@"name"]; 

40.    

41.  //查看設置後的值 

42.     NSLog(@"p name get by kvc is %@",[p valueForKey:@"name"]); 

43.   

44.  //效果和通過setValue 是一致的     

45.  p.name=@"name change by .name="; 

46.   

47.   //通過person自己的函數來更改name  

48.       [p changeName];  

49.   

50.   結果是 

51.  輸出 

52.  2011-07-03 16:35:57.406 Cocoa[13970:903] p.name is name 

53.  2011-07-03 16:35:57.418 Cocoa[13970:903] change happen, old:name   new:name kvc 

54.  2011-07-03 16:35:57.420 Cocoa[13970:903] name get by kvc is name kvc 

55.  2011-07-03 16:35:57.421 Cocoa[13970:903] change happen, old:name kvc   new:name change by .name= 

56.  最後一次修改是直接修改  所以沒法產生通知 

基本概念

MODEL

主要是英文文檔裏面經常出現的一些概念,講解一下,方便英文文檔的閱讀。

IOS應用開發是遵循MVC設計模式的,Cocoa框架用Object Modeling的規則來規範一個Model的實現。

ObjectModeling有如下幾個概念的規定:

Entity:表示持有數據的一個實體

Property實體中的成員,分爲Attribute和:Relationship

Attribute:基本類型的成員,比如:數字、NSString

Relationship:指向其它Entity的關係型成員,它又有to 1Relationshipto manyRelationship的區別。

AccessorMethodgettersetter

舉例:

如下是一個部門和員工關係的Model

部門:Department

 

部門名稱(NSString)

成員(NSArray)

部長(Employee)

MIC

(所有成員)

老王(一個成員)

MIB

 

 

員工:Employee

 

名字(NSStirng)

所屬部門(Department)

小王

MIC

 

 

 

使用KVCKVO的優勢

通過規定了一組通用的Cocoa命名法則、調用規則等,實現瞭如下功能: 

1)使用一對高度規範化的訪問方法,獲取以及設置任何對象的任何屬性的值。

2)通過繼承一個特定的方法,並且指定希望監視的對象及希望監視的屬性名稱,就能在該對象的指定屬性的值發生改變時,得到一個通知(儘管這不是一個真正意 義上的通知),並且得到相關屬性的值的變化(原先的值和改變後的新值)。

3)通過一個簡單的函數調用,使一個視圖對象的一個指定屬性隨時隨地都和一個控制器對象或模型對象的一個指定屬性保持同步。

KVC

概述

KVCKeyValue Coding的簡稱,它是一種可以直接通過字符串的名字(key)來訪問類屬性的機制。而不是通過調用SetterGetter方法訪問。

當使用KVOCore DataCocoaBindingsAppleScript(Mac支持)時,KVC是關鍵技術。

2、如何使用KVC

關鍵方法定義在:NSKeyValueCodingprotocol

KVC支持類對象和內建基本數據類型。

  獲取值

valueForKey:,傳入NSString屬性的名字。

valueForKeyPath:,傳入NSString屬性的路徑,xx.xx形式。

valueForUndefinedKey它的默認實現是拋出異常,可以重寫這個函數做錯誤處理。

  修改值

setValue:forKey:

setValue:forKeyPath:

setValue:forUndefinedKey:

setNilValueForKey: 當對非類對象屬性設置nil時,調用,默認拋出異常。

  一對多關係成員的情況

mutableArrayValueForKey:有序一對多關係成員  NSArray

mutableSetValueForKey:無序一對多關係成員  NSSet

3、KVC的實現細節

  搜索SetterGetter方法

 這一部分比較重要,能讓你瞭解到KVC調用之後,到底是怎樣獲取和設置類成員值的。

   搜索簡單的成員

     如:基本類型成員,單個對象類型成員:NSIntegerNSString*成員。

   a. setValue:forKey的搜索方式:

     首先搜索set<Key>:方法

      如果成員用@property@synthsize處理,因爲@synthsize告訴編譯器自動生成set<Key>:格式的setter方法,所以這種情況下會直接搜索到。

      注意:這裏的<Key>是指成員名,而且首字母大寫。下同。

     上面的setter方法沒有找到,如果類方法accessInstanceVariablesDirectly返回YES(注:這是NSKeyValueCodingCatogery中實現的類方法,默認實現爲返回YES)

     那麼按_<key>_is<Key><key>is<key>的順序搜索成員名。

     如果找到設置成員的值,如果沒有調用setValue:forUndefinedKey:

   b. valueForKey:的搜索方式:

1. 首先按get<Key><key>is<Key>的順序查找getter方法,找到直接調用。如果是boolint等內建值類型,會做NSNumber的轉換。

2. 上面的getter沒有找到,查找countOf<Key>objectIn<Key>AtIndex:<Key>AtIndexes格式的方法。

如果countOf<Key>和另外兩個方法中的一個找到,那麼就會返回一個可以響應NSArray所有方法的代理集合(collection proxy object)。發送給這個代理集合(collection proxy object)NSArray消息方法,就會以countOf<Key>objectIn<Key>AtIndex:<Key>AtIndexes這幾個方法組合的形式調用。還有一個可選的get<Key>:range:方法。

3. 還沒查到,那麼查找countOf<Key>enumeratorOf<Key>memberOf<Key>:格式的方法。

如果這三個方法都找到,那麼就返回一個可以響應NSSet所有方法的代理集合(collection proxy object)。發送給這個代理集合(collection proxy object)NSSet消息方法,就會以countOf<Key>enumeratorOf<Key>memberOf<Key>:組合的形式調用。

4. 還是沒查到,那麼如果類方法accessInstanceVariablesDirectly返回YES,那麼按_<key>_is<Key><key>is<key>的順序直接搜索成員名。

5. 再沒查到,調用valueForUndefinedKey:

查找有序集合成員,比如NSMutableArray

mutableArrayValueForKey:搜索方式如下:

1. 搜索insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex:或者insert<Key>:atIndexesremove<Key>AtIndexes:格式的方法。

如果至少一個insert方法和至少一個remove方法找到,那麼同樣返回一個可以響應NSMutableArray所有方法的代理集合。那麼發送給這個代理集合的NSMutableArray消息方法,以insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex:insert<Key>:atIndexesremove<Key>AtIndexes:組合的形式調用。還有兩個可選實現的接口:replaceObjectIn<Key>AtIndex:withObject:replace<Key>AtIndexes:with<Key>:

2. 否則,搜索set<Key>:格式的方法,如果找到,那麼發送給代理集合的NSMutableArray最終都會調用set<Key>:方法。

也就是說,mutableArrayValueForKey取出的代理集合修改後,用set<Key>:重新賦值回去。這樣做效率會差很多,所以推薦實現上面的方法。

3. 否則,那麼如果類方法accessInstanceVariablesDirectly返回YES,那麼按_<key><key>的順序直接搜索成員名。如果找到,那麼發送的NSMutableArray消息方法直接轉交給這個成員處理。

4. 再找不到,調用setValue:forUndefinedKey:

搜索無序集合成員,如:NSSet

mutableSetValueForKey:搜索方式如下:

1. 搜索add<Key>Object:remove<Key>Object:或者add<Key>:remove<Key>:格式的方法,如果至少一個insert方法和至少一個remove方法找到,那麼返回一個可以響應NSMutableSet所有方法的代理集合。那麼發送給這個代理集合的NSMutableSet消息方法,以add<Key>Object:remove<Key>Object:add<Key>:remove<Key>:組合的形式調用。還有兩個可選實現的接口:intersect<Key>set<Key>:

2. 如果recieverManagedObejct,那麼就不會繼續搜索了。

3. 否則,搜索set<Key>:格式的方法,如果找到,那麼發送給代理集合的NSMutableSet最終都會調用set<Key>:方法。也就是說,mutableSetValueForKey取出的代理集合修改後,用set<Key>:重新賦值回去。這樣做效率會差很多,所以推薦實現上面的方法。

4. 否則,那麼如果類方法accessInstanceVariablesDirectly返回YES,那麼按_<key><key>的順序直接搜索成員名。如果找到,那麼發送的NSMutableSet消息方法直接轉交給這個成員處理。

5. 再找不到,調用setValue:forUndefinedKey:

KVC還提供了下面的功能

值的正確性覈查

KVC提供屬性值確認的API,它可以用來檢查set的值是否正確、爲不正確的值做一個替換值或者拒絕設置新值並返回錯誤原因。

實現覈查方法

爲如下格式:validate<Key>:error:

如:

-(BOOL)validateName:(id *)ioValue error:(NSError **)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 NSLocalizedStringFromTable(  

                    @"A Person's name must be at least two characters long", @"Person",  

                    @"validation: too short name error");  

            NSDictionary *userInfoDict  

                [NSDictionary dictionaryWithObject:errorString  

                                            forKey:NSLocalizedDescriptionKey];  

            *outError [[[NSError alloc] initWithDomain:PERSON_ERROR_DOMAIN  

                                                    code:PERSON_INVALID_NAME_CODE  

                                                userInfo:userInfoDict] autorelease];  

         

        return NO;  

     

    return YES;  

 

調用覈查方法: 

validateValue:forKey:error:,默認實現會搜索 validate<Key>:error:格式的核查方法,找到則調用,未找到默認返回YES

注意其中的內存管理問題。

集合操作

集合操作通過對valueForKeyPath:傳遞參數來使用,一定要用在集合(如:array)上,否則產生運行時刻錯誤。其格式如下:

Left keypath部分:需要操作對象路徑。

Collectionoperator部分:通過@符號確定使用的集合操作。

Rightkey path部分:需要進行集合操作的屬性。

1、數據操作

@avg:平均值

@count:總數

@max:最大

@min:最小

@sum:總數

確保操作的屬性爲數字類型,否則運行時刻錯誤。

2、對象操作

針對數組的情況

@distinctUnionOfObjects:返回指定屬性去重後的值的數組

@unionOfObjects:返回指定屬性的值的數組,不去重

屬性的值不能爲空,否則產生異常。

3、數組操作

針對數組的數組情況

@distinctUnionOfArrays:返回指定屬性去重後的值的數組

@unionOfArrays:返回指定屬性的值的數組,不去重

@distinctUnionOfSets:同上,只是返回值爲NSSet

示例代碼:

效率問題

相比直接訪問KVC的效率會稍低一點,所以只有當你非常需要它提供的可擴展性時才使用它。

小結

KvoCocoa的一個重要機制,他提供了觀察某一屬性變化的方法,極大的簡化了代碼。這種觀察-被觀察模型適用於這樣的情況,比方說根據A(數 據類)的某個屬性值變化,Bview類)中的某個屬性做出相應變化。對於推崇MVCcocoa而言,kvo應用的地方非常廣泛。(這樣的機制聽起來類似Notification,但是notification是需要一個發送notification的對象,一般是 notificationCenter,來通知觀察者。而kvo是直接通知到觀察對象。)

適用kvo時,通常遵循如下流程:

1、註冊:

-(void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void*)context

keyPath就是要觀察的屬性值,options給你觀察鍵值變化的選擇,而context方便傳輸你需要的數據(注意這是一個void型)

2、實現變化方法:

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

change裏存儲了一些變化的數據,比如變化前的數據,變化後的數據;如果註冊時context不爲空,這裏context就能接收到。

是不是很簡單?kvo的邏輯非常清晰,實現步驟簡單。

說了這麼多,大家都要躍躍欲試了吧。可是,在此之前,我們還需要了解KVC機制。其實,知道了kvo的邏輯只是幫助你理解而已,要真正掌握的,不在於kvo的實現步驟是什麼,而在於KVC,因爲只有符合KVC標準的對象才能使用kvo(強烈推薦要使用kvo的人先理解KVC)。

KVC是一種間接訪問對象屬性(用字符串表徵)的機制,而不是直接調用對象的accessor方法或是直接訪問成員對象。

key就是確定對象某個值的字符串,它通常和accessor方法或是變量同名,並且必須以小寫字母開頭。Key path就是以“.”分隔的key,因爲屬性值也能包含屬性。比如我們可以person這樣的key,也可以有key.gender這樣的key path

獲取屬性值時可以通過valueForKey:的方法,設置屬性值用setValue:forKey:。與此同時,KVC還對未定義的屬性值定義了 valueForUndefinedKey:,你可以重載以獲取你要的實現(補充下,KVC定義載NSKeyValueCoding的非正式協議裏)。

O-C 2.0引入了property,我們也可以通過.運算符來訪問屬性。下面直接看個例子:

@property NSInteger number;

instance.number =
3;
[instance setValue:[NSNumber numberWithInteger:
3] forKey:@"number"];

注意KVC中的value都必須是對象。

以上介紹了通過KVC來獲取/設置屬性,接下來要說明下實現KVC的訪問器方法(accessor method)。Apple給出的慣例通常是:

key:,以及setKey:(使用的name conventionsettergetter命名一致)。對於未定義的屬性可以用setNilValueForKey:

至此,KVC的基本概念你應該已經掌握了。之所以是基本,因爲只涉及到了單值情況,kvc還可以運用到對多關係,這裏就不說了,留給各位自我學習的空間

接下來,我們要以集合爲例,來對掌握的KVC進行一下實踐。

之所以選擇array,因爲在ios中,array往往做爲tableview的數據源,有這樣的一種情況:

 假設我們已經有N條數據,在進行了某個操作後,有在原先的數據後多了2條記錄;或者對N中的某些數據進行更新替換。不使用KVC我們可以使用 reloadData方法或reloadRowsAtIndexPaths。前一種的弊端在於如果N很大消耗就很大。試想你只添加了幾條數據卻要重載之前 N數據。後一種方法的不足在於代碼會很冗餘,你要一次計算各個indexPath再去reload,而且還要提前想好究竟在哪些情況下會引起數據更新,

倘若使用了KVC/kvo,這樣的麻煩就迎刃而解了,你將不用關心追加或是更新多少條數據。

下面將以添加數據爲例,說明需要實現的方法:

實現insertObject:inKeyAtIndex:或者insertKey:atIndexes。同時在kvo中我們可以通過change這個dictionary得知發生了哪種變化,從而進行相應的處理。

KVC 就是一種通過字符串去間接操作對象屬性的機制, 

訪問一個對象屬性我們可以 person.age  也可以通過KVC的方式   [person valueForKey:@"age"]

keypath 就是屬性鏈式訪問  如 person.address.street  有點象java裏面的pojo  ognl表達式子類的

假如給出的字符串沒有對象的屬性 會訪問valueForUndefineKey方法 默認實現是raise 一個異常但你可以重寫這個方法, setValue的時候也是一樣的道理

key path accounts.transactions.payee would return an array with all the payee objects, for all the transactions, in all the accounts.

當設置一個非對象屬性爲nil時會拋異常, 但你也可以重寫方法

KVC就是一個在語言框架層面實現的觀察者模式 通過KVC的方式修改屬性時,會主動通知觀察者

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章