Objective-C:KVO

      KVO(鍵值觀察)是Objective-C提供的一種觀察對象屬性變化的機制,其內部是利用KVC技術來實現觀察者設計模型。利用KVO用戶能夠註冊一個對象爲另外一個對象的觀察者,並在被觀察對象的屬性發生變化時能收到通知。 css

1 使用KVO 框架

      利用鍵值觀察(Key Value Observing),能夠自動觀察其餘對象的變化。所以,當一個對象改變狀態(屬性)時,其它對象就會獲得通知。經過鍵值觀察,你不須要手動告訴其餘對象進行更新。它們會自動收到新值並執行適當的操做。這極其強大。設置是該技術最強大的應用之一,此外, Cocoa 框架中的核心數據和其餘技術也利用了鍵值觀察實現了一些奇妙的功能。 函數

     要使用KVO須要進行一些操做和配置: url

    1) 要使用鍵值觀察,被觀察的對象必須對所觀察的特性(屬性)使用符合 KVC 標準的存取器方法。 spa

    2) 想要觀察變化的對象,也就是觀察者,必須實現一個接收通知的回調方法。該方法是-observeValue:forKeyPath:ofObject:change:context:。該方法在值變化時被調用並能夠配置成同時接收新值和原值以及其餘自定義的信息。 設計

    3) 觀察者經過調用-addObserver:forKeyPath:options:context:方法針對被觀察對象進行註冊。調用該方法,告訴對象要觀察的 KVC 鍵路徑以及但願看到的變化,並提供一個在收到變化通知時能夠傳回的上下文對象。 code

      注意觀察者完成這些配置後,鍵路徑指定的屬性的任何變化均可以自動調用觀察者的回調方法。在觀察者完成對被觀察對象的觀察後,必須將本身移除。若是沒有作到這點而且觀察者以後就釋放了,未來向該觀察者發送通知時可能會致使應用崩潰。 server

2 註冊成爲觀察者 對象

      註冊成爲觀察者很容易。針對想要觀察的對象簡單調用-addObserver:forKeyPath:options:context:方法, blog

1 [obj addObserver:self
2 forKeyPath:@」memberVariable」
3 options:(NSKeyValueObservingOptionNiew | NSKeyValueObservingOptionOld)
4 context:NULL];

      Observer 參數一般是 self,這是在被觀察值變化時收到通知的對象。鍵路徑參數指定想要觀察的特性的鍵路徑。 options 參數指定一些標記來告訴 KVO 你但願變化如何傳給你。這些值能夠經過|操做符進行或操做。傳入的可能值如表 21所示。

表 21 傳入的可能值

功 能

NSKeyValueObservingOptionNew

做爲變動信息的一部分發送新值

NSKeyValueObservingOptionOld

做爲變動信息的一部分發送舊值

NSKeyValueObservingOptionInitial

在觀察者註冊時發送一個初始更新

NSKeyValueObservingOptionPrior

在變動先後分別發送變動,而不僅在變動後發送一次

      上下文參數是一個在 KVO系統中無變動傳遞的void*參數,即當被觀察的屬性發生變化時,會被傳回給觀測對象的回調方法。本質上,就 KVO 而言,該參數是不透明的數據塊,任何今後傳入參數的都會無變動傳遞的。

注意:

       記住使用 void*上下文參數時有和垃圾回收相關的特殊規則,你必須確保 void*指向的數據在以後訪問時仍然沒有被釋放並有效。換句話說,不要將一些存儲在棧上的值傳遞給該參數,這會致使崩潰。

3 實現KVO的回調方法

      使用 KVO 的第二步就是編寫觀察者的回調方法。以下的實現代碼顯示了-observeValue:forKeyPath:ofObject:change:context:方法的一個示例實現。

 1 -( void)observeValuseForKeyPath: (NSString*)inKeyPath
 2                       ofObject: (id)inObject
 3                         change: (NSDictionary*)inChange
 4                        context: ( void*)inCtx
 5 {
 6       if(inKeyPath isEqualToString:@」memverVariable」)
 7       {
 8             NSString *newValue = [inChange objectForKey:NSKeyValuseChangeNewKey];
 9       }
10       else  if([inKeyPath isEqualToString:@」…」])
11      {
12           …
13      }
14 }

      能夠從該方法看出,要作的第一件事情就是找出被觀察對象中變化的特性。該方法自動傳入一個對象參數,告訴你哪一個對象向你發送通知。經過對鍵路徑的傳入值使用-isEquals 方法,你能夠準確地肯定對象的什麼特性發生了改變。 Key 參數僅僅是一個字符串,和對 KVC 使用時同樣。所以,可使用 NSString 方法-isEqualToString:來肯定該通知所對應的鍵路徑。

      在肯定了對象的哪一個特性發生變化後,你能夠執行任何合適的操做。實際的變化經過 change參數傳遞給你。該參數是一個 NSDictionary 對象,包括和你註冊成爲觀察者時所請求的變化信息相關的鍵和值。這些鍵和值如表 22所示。

表 22 和變化信息相關的鍵值

NSKeyValueChangeKindKey

指定變化類型的 NSNumber

NSKeyValueChangeNewKey

新值

NSKeyValueChangeOldKey

原值

NSKeyValueChangeIndexeskey

若是 NSKeyValueChangeKindKey 是 NSKeyValueChangeInsertion、 NSKeyValueChangeRemoval、NSKeyValueChangeReplacement 之一,該值就包含變化值的索引

NSKeyValueChangeNotificationIsPriorKey

和 NSKeyValueChangeOptionPrior 結合使用來表示這是"以前"的通知

      能夠看出,若是選擇同時接收原值和新值,兩個都會在變化參數中提供,經過合適的鍵就能夠訪問到。從變化字典中獲取到值以後,就能夠在對象中使用它執行任何須要的操做。

      記住 KVC 必須使用對象來發送值——不能直接使用標量和結構體。所以,若是所觀察的值是標量或者結構體,所接收的值就分別是 NSNumber 或 NSValue 類型的。所以,必須從該值中提取出實際須要的標量或者結構體值。上述示例代碼就展現了這一點。

NSKeyValueChangeKindKey 指定了接收到的變化類型。可能的類型如表 23所示。

表 23 可能的變化類型

功 能

NSKeyValueChangeSetting

指定該值被設置

NSKeyValueChangeInsertion

指定這些值插入到集合或者一對多的關係中

NSKeyValueChangeRemoval

指定這些值在一對多的關係中被移除

NSKeyValueChangeReplacement

指定這些值在一對多的關係中被替換

 

4 移除觀測者

       記住在結束對一個對象變化的觀察後,須要移除觀察者。若是不這樣,應用可能會崩潰。

爲了移除觀察者,只須要調用方法-removeObserver:forKeyPath:,並傳入觀察者做爲第一個參數,觀察的鍵路徑是第二個參數。代碼清單 6-16 顯示了一個在觀察者的 dealloc 方法中實現的示例。

1 -( void) dealloc:
2 {
3      [obj removeObserver: self forKeyPath:@」memberVariable」];
4      [super dealloc];
5 }

說明:

       在垃圾回收的環境中,若是忘記移除觀察者可能不會形成崩潰。可是,移除觀察者還是一種好的作法,這樣就能夠在不支持垃圾回收的環境中造成該習慣。

5 實現手動通知

      全部的這些通知都自動發生。須要作的就是爲屬性提供符合 KVC 標準的存取器方法,其餘一切都會正常工做。有時,不必定想利用自動通知。有時想在改變一個值或者一組值時手動發送通知。好比,若是須要一次性進行不少變動,可能會想將這些變化打包後一塊兒發送。在這些狀況下就會使用手動通知。

      爲了實現手動通知,必須重寫類方法+automaticallyNotifiesObserversForKey:來告訴 Objective-C 你不想自動通知觀察者所發生的變化。能夠經過對任意一個想進行手動通知的鍵返回 NO 來實現。示例如代碼清單 6-17 所示。

1 +(BOOL)automaticallyNotifiesObserversForKey:(NSString*)inKey
2 {
3      if([inKey isEqualToString:@」memberVariable」])
4          return NO;
5      return YES;
6 }

       若是想要手動通知所發生的變化,你必須在變化以前調用方法-willChangeValueForKey:,而後在變化以後調用方法-didChangeValueForKey:。示例如代碼清單 6-18 所示。

1 -( void)setMemberVariable:(CGFloat)inValue
2 {
3     [self willChangeValueForKey:@」memberVariable」];
4     memberVariable = inValue;
5     [self didChangeValueForKey:@」memberVariable」];
6 }

       這些調用在須要時是能夠嵌套的,好比在一次調用中須要修改多個變量的狀況。此外還有和一對多關係對應的調用。它們是-willChange:valuesAtIndexes:forKey:和-didChange:valuesForIndexes:forKey:。

 

6 使用KVO的風險

      使用 KVO 也會遇到問題,更具體點說,使用 KVO 最大的風險就是若是觀察者觀察每一步,這些觀察者可能會執行其餘操做,由於你沒法控制這些觀察者,因此也就沒法控制這些操做。

      大多數狀況下這不會成爲一個問題,但也在例外。這種狀況就是在初始化函數或者 dealloc函數中使用存取器方法來釋放變量成員時,如代碼清單 6-19 所示。

1 -( void)dealloc
2 {
3      [self setFoo:nil];
4      [self setBar:nil];
5      [super dealloc];
6 }

       按這種方式寫 dealloc 方法很好!能夠在釋放成員變量的同時將它設成nil,一步搞定。問題就是,在調用這些存取器方法時, KVO 觀察者會在這些變化發生時收到通知。若是他們不想接收 nil 或者但願在收到通知時可以處理對象自己,此時就會發生一些糟糕的事情。此外,若是想到了觀察者在收到 bar 變量的變化通知時,但願能夠訪問 foo 變量,這種狀況下就會形成一個問題,由於 foo 變量已經被釋放而且被設置成 nil。

       蘋果目前推薦的作法就是不要在初始化函數或者 dealloc 方法中經過存取器方法初始化和釋放成員變量。這種狀況在 64 位運行時中變得更復雜,由於它能夠在沒有相關的成員變量的狀況下聲明屬性。在這些狀況中,初始化和釋放成員變量的惟一辦法就是經過存取器方法。

我是使用存取器方法來初始化和釋放成員變量的,除非我知道在給定的環境中這樣作會形成問題。此外,實現鍵值觀察者時,我會確保觀測者能夠正確處理 nil 值並儘可能最小化反作用。

      若是你以爲這種風險是值得的,那就經過存取器方法來編寫初始化函數和釋放函數吧。只要意識到可能的危險,在遇到問題時,就能夠立刻知道應該從哪裏查找。另外一方面,若是你不能確保觀察者會這麼作的話,那就就遵循蘋果的建議,除非不得已,不然不要在初始化函數和析構函數中使用存取器方法。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息