IOS SDK詳解之KVO
前言:KVC和KVO是協助我們駕馭objective C動態特性工具。KVO是建立在KVC基礎上的,所以不瞭解KVC的同學可以參見我的這篇部落格。這裡我不會再重複講解KVC。
本文的內容
KVO的定義
KVO的典型使用情境。
手動KVO
幾點KVO要說的地方
一 KVO的定義
KVO提供了一種key-value-observing的機制,也就是說可以通過監聽key,來獲得value的變化。用來在對象之間監聽狀態變化。使用KVO的類要遵循 協議,事實上,任何繼承自NSObject的類,都遵循了這個協議。而Object C中,幾乎所有的類都源自NSObject
使用KVO通常分為三步
1.1 訂閱想要監聽的keypath
用函數
- (void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
註冊通知
observer:觀察者,也就是KVO通知的訂閱者。訂閱著必須實現
observeValueForKeyPath:ofObject:change:context:方法 keyPath:描述將要觀察的屬性,相對於被觀察者。 options:KVO的一些屬性配置;有四個選項。 context: 上下文,這個會傳遞到訂閱著的函數中,用來區分訊息,所以應當是不同的。
options所包括的內容
NSKeyValueObservingOptionNew:change字典包括改變後的值 NSKeyValueObservingOptionOld:change字典包括改變前的值 NSKeyValueObservingOptionInitial:註冊後立刻觸發KVO通知 NSKeyValueObservingOptionPrior:值改變前是否也要通知(這個key決定了是否在改變前改變後通知兩次)
1.2 響應狀態變化
每當監聽的keyPath發生變化了,就會在這個函數中回調。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
keyPath:被監聽的keyPath , 用來區分不同的KVO監聽。 object: 被觀察修改後的對象(可以通過object獲得修改後的值) change:儲存資訊改變的字典(可能有舊的值,新的值等) context:上下文,用來區分不同的KVO監聽。
1.3 在適當的時候,取消訂閱
通常使用兩個函數
- (void)removeObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context
一般在remove的時候會這麼寫,因為remove的時候,無法判讀啊remove是否成功了
@try { [object removeObserver:self forKeyPath:keyPath]; } @catch (NSException *exception) { NSLog(@%@,exception); }
二 KVO的典型使用情境 - model 與 view的同步
這裡,你將看到一個完整的KVO的例子。和上篇KVC一樣,我寫了個類似的demo。點擊random會隨機改變User的age,然後UI上要進行同步顯示出新的和舊的age。
實現過程如下:
定義了一個User類來作為Model
@interface User : NSObject@property (strong,nonatomic) NSString * name;@property (nonatomic) NSUInteger age;@end
定義兩個靜態變數,一個作為keyPath,一個作為context
static NSString * observename = @age;static void * privateContext = 0;
然後在viewWillAppear中註冊(訂閱)KVO,在viewWillDisappear中刪除KVO
-(void)viewWillAppear:(BOOL)animated{ [self.user addObserver:self forKeyPath:observename options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:privateContext];}-(void)viewWillDisappear:(BOOL)animated{ @try { [self.user removeObserver:self forKeyPath:observename]; } @catch (NSException *exception) { NSLog(@%@,exception); }}
當點擊random的時候,age會改變
- (IBAction)random:(id)sender { self.user.age = arc4random()%100 +1;}
然後,在上面提到的函數中進行model和view的同步
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ if (context == privateContext) { if ([keyPath isEqualToString:observename]) { NSNumber * old = [change objectForKey:NSKeyValueChangeOldKey]; NSNumber * new = [change objectForKey:NSKeyValueChangeNewKey]; self.lastvalue.text = [NSString stringWithFormat:@%@,old]; self.newvalue.text = [NSString stringWithFormat:@%@,new]; } }}
三 手動KVO
KVO的實現,是對註冊的keyPath中自動實現了兩個函數,在Setter中,自動調用。
- (void)willChangeValueForKey:(NSString *)key- (void)didChangeValueForKey:(NSString *)key
可能有時候,我們要實現手動的KVO
這時候需要關閉自動產生KVO通知,然後手動的調用,手動通知的好處就是,可以靈活加上自己想要的判斷條件。例如
+(BOOL)automaticallyNotifiesObserversOfAge{ return NO;}-(void)setAge:(NSUInteger)age{ if (age < 22) { return; } [self willChangeValueForKey:@age]; _age = age; [self didChangeValueForKey:@age];}
四 KVO要提到的幾點KVO和Context
由於Context通常用來區分不同的KVO,所以context的唯一性很重要。通常,我的使用方式是通過在當前.m檔案裡用靜態變數定義。
static void * privateContext = 0;
KVO與線程
KVO的響應和KVO觀察的值變化是在一個線程上的,所以,大多數時候,不要把KVO與多線程混合起來。除非能夠保證所有的觀察者都能安全執行緒的處理KVO
監聽變化的值
改變前和改變後分別為
id oldValue = change[NSKeyValueChangeOldKey];id newValue = change[NSKeyValueChangeNewKey];