天天看點

iOS程式設計——Objective-C KVO/KVC機制[轉]

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

先上代碼 

1.     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] p 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 1relationship和to manyrelationship的差別。

accessormethod:getter,setter。

舉例:

如下是一個部門和員工關系的model

部門:department

部門名稱(nsstring)

成員(nsarray)

部長(employee)

mic

(所有成員)

老王(一個成員)

mib

員工:employee

名字(nsstirng)

所屬部門(department)

小王

使用kvc、kvo的優勢

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

1)使用一對高度規範化的通路方法,擷取以及設定任何對象的任何屬性的值。

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

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

kvc

1 、概述

kvc是keyvalue coding的簡稱,它是一種可以直接通過字元串的名字(key)來通路類屬性的機制。而不是通過調用setter、getter方法通路。

當使用kvo、core data、cocoabindings、applescript(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的實作細節

  搜尋setter、getter方法

 這一部分比較重要,能讓你了解到kvc調用之後,到底是怎樣擷取和設定類成員值的。

   搜尋簡單的成員

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

   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方法,找到直接調用。如果是bool、int等内建值類型,會做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>:atindexes、remove<key>atindexes:格式的方法。

如果至少一個insert方法和至少一個remove方法找到,那麼同樣傳回一個可以響應nsmutablearray所有方法的代理集合。那麼發送給這個代理集合的nsmutablearray消息方法,以insertobject:in<key>atindex:、removeobjectfrom<key>atindex:、insert<key>:atindexes、remove<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. 如果reciever是managedobejct,那麼就不會繼續搜尋了。

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的效率會稍低一點,是以隻有當你非常需要它提供的可擴充性時才使用它。

小結

kvo是cocoa的一個重要機制,他提供了觀察某一屬性變化的方法,極大的簡化了代碼。這種觀察-被觀察模型适用于這樣的情況,比方說根據a(數 據類)的某個屬性值變化,b(view類)中的某個屬性做出相應變化。對于推崇mvc的cocoa而言,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 convention和setter/getter命名一緻)。對于未定義的屬性可以用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的方式修改屬性時,會主動通知觀察者

from:http://blog.csdn.net/catandrat111/article/details/8556455

歡迎加群互相學習,共同進步。qq群:ios: 58099570 | android: 330987132 | go:217696290 | python:336880185 | 做人要厚道,轉載請注明出處!

繼續閱讀