Key-Value Observing機制


知識點介紹
Key-Value Observing (簡寫為KVO):當指定的對象的屬性被修改了,允許對象接受到通知的機制。每次指定的被觀察對象的屬性被修改的時候,KVO都會自動的去通知相應的觀察者。


KVO的優點:
當 有屬性改變,KVO會提供自動的消息通知。這樣的架構有很多好處。首先,開發人員不需要自己去實現這樣的方案:每次屬性改變了就發送消息通知。這是KVO 機制提供的最大的優點。因為這個方案已經被明確定義,獲得框架級支持,可以方便地采用。開發人員不需要添加任何代碼,不需要設計自己的觀察者模型,直接可 以在工程里使用。其次,KVO的架構非常的強大,可以很容易的支持多個觀察者觀察同一個屬性,以及相關的值。
KVO如何工作:
需要三個步驟來建立一個屬性的觀察員。理解這三個步驟就可以知道KVO如何設計工作的。 (1)首先,構思一下如下實現KVO是否有必要。比如,一個對象,當另一個對象的特定屬性改變的時候,需要被通知到。


例 如,PersonObject希望能夠覺察到BankObject對象的accountBalance屬性的任何變化。 (2)那么 PersonObject必須發送一個“addObserver:forKeyPath:options:context:”消息,注冊成為 BankObject的accountBalance屬性的觀察者。(說 明:“addObserver:forKeyPath:options:context:”方法在指定對象實例之間建立了一個連接。注意,這個連接不是兩 個類之間建立的,而是兩個對象實例之間建立的。) (3)為了能夠響應消息,觀察者必須實現 “observeValueForKeyPath:ofObject:change:context:”方法。這個方法實現如何響應變化的消息。在這個方 法里面我們可以跟自己的情況,去實現應對被觀察對象屬性變動的相應邏輯。 (4)假如遵循KVO規則的話,當被觀察的屬性改變的話,方法 “observeValueForKeyPath:ofObject:change:context:”會自動被調用。
參考資料
http://www.cocoadev.cn/CocoaDev/Key-Value-Observing-Quick-Start-cn.asp


本知識點在此例中的應用

//注冊監聽@implementation RootViewController
- (void)viewDidLoad
{
//監聽屬性“earthquakeList”
/*
KVO: listen for changes to our earthquake data source for table
view updates
*/
[self addObserver:self
forKeyPath:@"earthquakeList" options:0 context:NULL];
}@end
//屬性發生改變時
- (void)insertEarthquakes:(NSArray *)earthquakes
{
// this will allow us as an observer to notified
(see observeValueForKeyPath)*/
// so we can update our UITableView
[self willChangeValueForKey:@"earthquakeList"];
[self.earthquakeList addObjectsFromArray:earthquakes];
[self didChangeValueForKey:@"earthquakeList"];
}
//當屬性的值發生變化時,自動調用此方法
/*
listen for changes to the earthquake list coming from our app delegate. */
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
[self.tableView reloadData];
}

 

 

cocoa的KVO模型中,有兩種通知觀察者的方式,自動通知和手動通知。顧名思義,自動通知由cocoa在屬性值變化時自動通知觀察者,而手動通知需要在值變化時調用 willChangeValueForKey:和didChangeValueForKey: 方法通知調用者。為求簡便,我們一般使用自動通知。

要使用手動通知,需要在 automaticallyNotifiesObserversForKey方法中明確告訴cocoa,哪些鍵值要使用自動通知:

//重新實現NSObject類中的automaticallyNotifiesObserversForKey:方法,返回yes表示自動通知。  
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key
{
//當這兩個值改變時,使用自動通知已注冊過的觀察者,觀察者需要實現observeValueForKeyPath:ofObject:change:context:方法
if ([key isEqualToString:@"isFinished"])
{
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}

手動通知在需要改變值_isFinished變量的地方,使用

[self willChangeValueForKey:@"isFinished"]; 
finished = YES;
[self didChangeValueForKey:@"isFinished"];


自動通知在需要改變_isFinished變量的地方,使用

[self setValue:[NSNumber numberWithBool:YES] forKey:@"isFinished"]; 

 

方法,而不是僅僅使用簡單賦值。

我們需要在3個地方改變isFinished值為YES,請求結束時、連接出錯誤,線程被cancel。請在對應的方法代碼中加入上面的語句。

最后,需要在觀察者的代碼中進行注冊。打開ViewController中調用NSOperation子類的地方,加入:

 

    //kvo注冊  
[operation addObserver:self forKeyPath:@"isFinished"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:operation];
並實現 observeValueForKeyPath 方法:
//接收變更通知
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqual:@"isFinished"]) {
BOOL isFinished=[[change objectForKey:NSKeyValueChangeNewKey] intValue];
if (isFinished) {//如果服務器數據接收完畢
[indicatorView stopAnimating];
URLOperation* ctx=(URLOperation*)context;
NSStringEncoding enc=CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
NSLog(@"%@",[[NSString alloc] initWithData:[ctx data] encoding:enc]);
//取消kvo注冊
[ctx removeObserver:self
forKeyPath:@"isFinished"];
}
}else{
// be sure to call the super implementation
// if the superclass implements it
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}

 

 

一,概述

KVO,即:Key-Value Observing,它提供一種機制,當指定的對象的屬性被修改后,則對象就會接受到通知。簡單的說就是每次指定的被觀察的對象的屬性被修改后,KVO就會自動通知相應的觀察者了。

二,使用方法

系統框架已經支持KVO,所以程序員在使用的時候非常簡單。

1. 注冊,指定被觀察者的屬性,

2. 實現回調方法

3. 移除觀察

三,實例:

假設一個場景,股票的價格顯示在當前屏幕上,當股票價格更改的時候,實時顯示更新其價格。

1.定義DataModel,

@interface StockData : NSObject {  
NSString * stockName;
float price;
}
@end
@implementation StockData
@end

 

2.定義此model為Controller的屬性,實例化它,監聽它的屬性,並顯示在當前的View里邊


- (void)viewDidLoad  
{
[super viewDidLoad];

stockForKVO = [[StockData alloc] init];
[stockForKVO setValue:@"searph" forKey:@"stockName"];
[stockForKVO setValue:@"10.0" forKey:@"price"];
[stockForKVO addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];

myLabel = [[UILabel alloc]initWithFrame:CGRectMake(100, 100, 100, 30 )];
myLabel.textColor = [UIColor redColor];
myLabel.text = [stockForKVO valueForKey:@"price"];
[self.view addSubview:myLabel];

UIButton * b = [UIButton buttonWithType:UIButtonTypeRoundedRect];
b.frame = CGRectMake(0, 0, 100, 30);
[b addTarget:self action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:b];

}



3.當點擊button的時候,調用buttonAction方法,修改對象的屬性

 

-(void) buttonAction  
{
[stockForKVO setValue:@"20.0" forKey:@"price"];
}

 

4. 實現回調方法

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context  
{
if([keyPath isEqualToString:@"price"])
{
myLabel.text = [stockForKVO valueForKey:@"price"];
}
}

5.增加觀察與取消觀察是成對出現的,所以需要在最后的時候,移除觀察者

- (void)dealloc  
{
[super dealloc];
[stockForKVO removeObserver:self forKeyPath:@"price"];
[stockForKVO release];
}

四,小結

KVO這種編碼方式使用起來很簡單,很適用與datamodel修改后,引發的UIVIew的變化這種情況,就像上邊的例子那樣,當更改屬性的值后,監聽對象會立即得到通知。







KVC、KVO即NSKeyValueCoding和NSKeyValueObserving的簡稱。
那我們KVO、KVC用來做什么的我們又怎么使用它呢?

首先我們先了解下KVO的機制

KVO:當指定的對象的屬性被修改了,允許對象接收到通知的機制。每當在類中定義一個監聽

如:

[self addObserver:self forKeyPath:@"items"  
      options:0  context:contexStr];


當然你還可以監聽其他對象的屬性變化

[person addObserver:money forKeyPath:@"account"

      options:0 context:contexStr];


只要當前類中items這個屬性發生的變化都會觸發到以下的方法。

- (void)observeValueForKeyPath:(NSString *)keyPath

ofObject:(id)object

change:(NSDictionary *)change

context:(void *)context


KVO的優點:

當有屬性改變,KVO會提供自動的消息通知。這樣開發人員不需要自己去實現這樣的方案:每次屬性改變了就發送消息通知。

這是KVO機制提供的最大的優點。因為這個方案已經被明確定義,獲得框架級支持,可以方便地采用。

開發人員不需要添加任何代碼,不需要設計自己的觀察者模型,直接可以在工程里使用。

其次,KVO的架構非常的強大,可以很容易的支持多個觀察者觀察同 一個屬性,以及相關的值。

KVC的實現分析

KVC運用了一個isa-swizzling技術。

isa-swizzling就是類型混合指針機制。KVC主要通過isa-swizzling,來實現其內部查找定位的。

isa指針,就是is a kind of的意思,指向維護分發表的對象的類。該分發表實際上包含了指向實現類中的方法的指針,和其它數據。

如下KVC的代碼:

[person setValue:@"personName" forKey:@"name"];

就會被編譯器處理成:

SEL sel = sel_get_uid ("setValue:forKey:");

IMP method = objc_msg_lookup (person->isa,sel);

method(person, sel, @"personName", @"name");


其中:

SEL數據類型:它是編譯器運行Objective-C里的方法的環境參數。

IMP數據類型:他其實就是一個 編譯器內部實現時候的函數指針。當Objective-C編譯器去處理實現一個方法的時候,就會指向一個IMP對象,這個對象是C語言表述的類型。

***

KVC在調用方法setValue的時候

(1)首先根據方法名找到運行方法的時候所需要的環境參數。

(2)他會從自己isa指針結合環境參數,找到具體的方法實現的接口。

(3)再直接查找得來的具體的方法實現。

這樣的話前面介紹的KVO實現就好理解了

當一個對象注冊了一個觀察者,被觀察對象的isa指針被修改的時候,isa指針就會指向一個中間類,而不是真實的類。

所以isa指針其實不需要指向實例對象真實的類。所以我們的程序最好不要依賴於isa指針。在調用類的方法的時候,最好要明確對象實例的類名。

這樣只有當我們調用KVC去訪問key值的時候KVO才會起作用。所以肯定確定的是,KVO是基於KVC實現的。

 
 
 

KVO簡而言之就是:基於鍵值的觀察者,實際上就是觀察者模式。

Cocoa Framework已經為我們提供了這一模式,不需要我們自己來實現了。我們只需要按照約定的方式去做就可以了。KVO主要用於用戶界面交互,當多個View共同使用了同一個實體,當這個實體中的某個屬性改變時,如果需要更新多個界面,KVO的作用就發揮出來了。


下面給出一個簡單的示例,來展示如何使用KVO。

示例下載

有兩種方式可以在鍵值改變的時候給觀察者發送通知:自動方式和手動方式。其中自動方式是由NSObject提供的一個默認實現,通常情況下,如果你自定義了一個類是從NSObject繼承而來,那么該類就已經具有了KVO的自動通知功能,而且不需要額外的編寫代碼。如果需要手動控制通知方式,那么需要重寫automaticallyNotifiesObserversForKey:方法。在該方法中如果需要手動控制通知方式,則將automaticallyNotifiesObserversForKey:返回NO,否則返回YES。

下面的例子是將openingBalance屬性設置為手動通知方式,其他屬性默認為自動通知方式

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

 

手動通知方式的好處在於可以減少不必要的通知,比如你可以首先檢測一下該屬性值是否發生改變,如果發生改變則通知,否則不通知,代碼示例如下:

 - (void)setOpeningBalance:(double)theBalance {

if (theBalance != openingBalance) {

[self willChangeValueForKey:@"openingBalance"];

openingBalance=theBalance;

[self didChangeValueForKey:@"openingBalance"];

}

}




如果一個單一的操作引發了多個屬性值的改變,那么就必須嵌套改變通知。代碼示例如下:

- (void)setOpeningBalance:(double)theBalance {

[self willChangeValueForKey:@"openingBalance"];

[self willChangeValueForKey:@"itemChanged"];

openingBalance=theBalance;

itemChanged=itemChanged+1;

[self didChangeValueForKey:@"itemChanged"];

[self didChangeValueForKey:@"openingBalance"];

}



 


 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM