Key-Value Observing機(jī)制
知識點(diǎn)介紹
Key-Value Observing (簡寫為KVO):當(dāng)指定的對象的屬性被修改了,允許對象接受到通知的機(jī)制厉亏。每次指定的被觀察對象的屬性被修改的時(shí)候,KVO都會自動的去通知相應(yīng)的觀察者抗悍。
KVO的優(yōu)點(diǎn):
當(dāng) 有屬性改變宜猜,KVO會提供自動的消息通知。這樣的架構(gòu)有很多好處秫舌。首先,開發(fā)人員不需要自己去實(shí)現(xiàn)這樣的方案:每次屬性改變了就發(fā)送消息通知绣檬。這是KVO 機(jī)制提供的最大的優(yōu)點(diǎn)足陨。因?yàn)檫@個(gè)方案已經(jīng)被明確定義,獲得框架級支持娇未,可以方便地采用墨缘。開發(fā)人員不需要添加任何代碼,不需要設(shè)計(jì)自己的觀察者模型,直接可 以在工程里使用镊讼。其次宽涌,KVO的架構(gòu)非常的強(qiáng)大,可以很容易的支持多個(gè)觀察者觀察同一個(gè)屬性蝶棋,以及相關(guān)的值卸亮。
KVO如何工作:
需要三個(gè)步驟來建立一個(gè)屬性的觀察員。理解這三個(gè)步驟就可以知道KVO如何設(shè)計(jì)工作的玩裙。 (1)首先兼贸,構(gòu)思一下如下實(shí)現(xiàn)KVO是否有必要。比如吃溅,一個(gè)對象溶诞,當(dāng)另一個(gè)對象的特定屬性改變的時(shí)候,需要被通知到决侈。
例 如螺垢,PersonObject希望能夠覺察到BankObject對象的accountBalance屬性的任何變化。 (2)那么 PersonObject必須發(fā)送一個(gè)“addObserver:forKeyPath:options:context:”消息颜及,注冊成為 BankObject的accountBalance屬性的觀察者甩苛。(說 明:“addObserver:forKeyPath:options:context:”方法在指定對象實(shí)例之間建立了一個(gè)連接蹂楣。注意俏站,這個(gè)連接不是兩 個(gè)類之間建立的,而是兩個(gè)對象實(shí)例之間建立的痊土。) (3)為了能夠響應(yīng)消息肄扎,觀察者必須實(shí)現(xiàn) “observeValueForKeyPath:ofObject:change:context:”方法。這個(gè)方法實(shí)現(xiàn)如何響應(yīng)變化的消息赁酝。在這個(gè)方 法里面我們可以跟自己的情況犯祠,去實(shí)現(xiàn)應(yīng)對被觀察對象屬性變動的相應(yīng)邏輯。 (4)假如遵循KVO規(guī)則的話酌呆,當(dāng)被觀察的屬性改變的話衡载,方法 “observeValueForKeyPath:ofObject:change:context:”會自動被調(diào)用。
參考資料
http://www.cocoadev.cn/CocoaDev/Key-Value-Observing-Quick-Start-cn.asp
本知識點(diǎn)在此例中的應(yīng)用
復(fù)制代碼
//注冊監(jiān)聽@implementation RootViewController
- (void)viewDidLoad
{
//監(jiān)聽屬性“earthquakeList”
/* KVO: listen for changes to our earthquake data source for table
view updates*/
[self addObserver:self
forKeyPath:@"earthquakeList" options:0 context:NULL];
}@end
//屬性發(fā)生改變時(shí)
- (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"];
}
//當(dāng)屬性的值發(fā)生變化時(shí)隙袁,自動調(diào)用此方法
/* 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在屬性值變化時(shí)自動通知觀察者,而手動通知需要在值變化時(shí)調(diào)用 willChangeValueForKey:和didChangeValueForKey: 方法通知調(diào)用者娜饵。為求簡便坡贺,我們一般使用自動通知。
要使用手動通知,需要在 automaticallyNotifiesObserversForKey方法中明確告訴cocoa遍坟,哪些鍵值要使用自動通知:
復(fù)制代碼
//重新實(shí)現(xiàn)NSObject類中的automaticallyNotifiesObserversForKey:方法拳亿,返回yes表示自動通知。
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key
{
//當(dāng)這兩個(gè)值改變時(shí)愿伴,使用自動通知已注冊過的觀察者风瘦,觀察者需要實(shí)現(xiàn)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個(gè)地方改變isFinished值為YES官帘,請求結(jié)束時(shí)瞬雹、連接出錯(cuò)誤,線程被cancel刽虹。請?jiān)趯?yīng)的方法代碼中加入上面的語句酗捌。
最后,需要在觀察者的代碼中進(jìn)行注冊涌哲。打開ViewController中調(diào)用NSOperation子類的地方胖缤,加入:
復(fù)制代碼
//kvo注冊
[operation addObserver:self forKeyPath:@"isFinished"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:operation];
并實(shí)現(xiàn) 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) {//如果服務(wù)器數(shù)據(jù)接收完畢
[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阀圾,它提供一種機(jī)制哪廓,當(dāng)指定的對象的屬性被修改后,則對象就會接受到通知初烘。簡單的說就是每次指定的被觀察的對象的屬性被修改后涡真,KVO就會自動通知相應(yīng)的觀察者了。
二肾筐,使用方法
系統(tǒng)框架已經(jīng)支持KVO哆料,所以程序員在使用的時(shí)候非常簡單。
注冊吗铐,指定被觀察者的屬性东亦,
實(shí)現(xiàn)回調(diào)方法
移除觀察
三,實(shí)例:
假設(shè)一個(gè)場景,股票的價(jià)格顯示在當(dāng)前屏幕上唬渗,當(dāng)股票價(jià)格更改的時(shí)候典阵,實(shí)時(shí)顯示更新其價(jià)格。
1.定義DataModel谣妻,
@interface StockData : NSObject {
NSString * stockName;
float price;
}
@end
@implementation StockData
@end
2.定義此model為Controller的屬性萄喳,實(shí)例化它,監(jiān)聽它的屬性蹋半,并顯示在當(dāng)前的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.當(dāng)點(diǎn)擊button的時(shí)候他巨,調(diào)用buttonAction方法,修改對象的屬性
-(void) buttonAction
{
[stockForKVO setValue:@"20.0" forKey:@"price"];
}
- 實(shí)現(xiàn)回調(diào)方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:@"price"])
{
myLabel.text = [stockForKVO valueForKey:@"price"];
}
}
5.增加觀察與取消觀察是成對出現(xiàn)的,所以需要在最后的時(shí)候染突,移除觀察者
- (void)dealloc
{
[super dealloc];
[stockForKVO removeObserver:self forKeyPath:@"price"];
[stockForKVO release];
}
四捻爷,小結(jié)
KVO這種編碼方式使用起來很簡單,很適用與datamodel修改后份企,引發(fā)的UIVIew的變化這種情況也榄,就像上邊的例子那樣,當(dāng)更改屬性的值后司志,監(jiān)聽對象會立即得到通知甜紫。
KVC、KVO即NSKeyValueCoding和NSKeyValueObserving的簡稱骂远。
那我們KVO囚霸、KVC用來做什么的我們又怎么使用它呢?
首先我們先了解下KVO的機(jī)制
KVO:當(dāng)指定的對象的屬性被修改了激才,允許對象接收到通知的機(jī)制拓型。每當(dāng)在類中定義一個(gè)監(jiān)聽
如:
[self addObserver:self forKeyPath:@"items"
options:0 context:contexStr];
當(dāng)然你還可以監(jiān)聽其他對象的屬性變化
[person addObserver:money forKeyPath:@"account"
options:0 context:contexStr];
只要當(dāng)前類中items這個(gè)屬性發(fā)生的變化都會觸發(fā)到以下的方法。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
KVO的優(yōu)點(diǎn):
當(dāng)有屬性改變瘸恼,KVO會提供自動的消息通知劣挫。這樣開發(fā)人員不需要自己去實(shí)現(xiàn)這樣的方案:每次屬性改變了就發(fā)送消息通知。
這是KVO機(jī)制提供的最大的優(yōu)點(diǎn)东帅。因?yàn)檫@個(gè)方案已經(jīng)被明確定義压固,獲得框架級支持,可以方便地采用冰啃。
開發(fā)人員不需要添加任何代碼邓夕,不需要設(shè)計(jì)自己的觀察者模型刘莹,直接可以在工程里使用阎毅。
其次,KVO的架構(gòu)非常的強(qiáng)大点弯,可以很容易的支持多個(gè)觀察者觀察同 一個(gè)屬性扇调,以及相關(guān)的值。
KVC的實(shí)現(xiàn)分析
KVC運(yùn)用了一個(gè)isa-swizzling技術(shù)抢肛。
isa-swizzling就是類型混合指針機(jī)制狼钮。KVC主要通過isa-swizzling,來實(shí)現(xiàn)其內(nèi)部查找定位的捡絮。
isa指針,就是is a kind of的意思,指向維護(hù)分發(fā)表的對象的類熬芜。該分發(fā)表實(shí)際上包含了指向?qū)崿F(xiàn)類中的方法的指針,和其它數(shù)據(jù)福稳。
如下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數(shù)據(jù)類型:它是編譯器運(yùn)行Objective-C里的方法的環(huán)境參數(shù)涎拉。
IMP數(shù)據(jù)類型:他其實(shí)就是一個(gè) 編譯器內(nèi)部實(shí)現(xiàn)時(shí)候的函數(shù)指針。當(dāng)Objective-C編譯器去處理實(shí)現(xiàn)一個(gè)方法的時(shí)候,就會指向一個(gè)IMP對象鼓拧,這個(gè)對象是C語言表述的類型半火。
KVC在調(diào)用方法setValue的時(shí)候
(1)首先根據(jù)方法名找到運(yùn)行方法的時(shí)候所需要的環(huán)境參數(shù)。
(2)他會從自己isa指針結(jié)合環(huán)境參數(shù)季俩,找到具體的方法實(shí)現(xiàn)的接口钮糖。
(3)再直接查找得來的具體的方法實(shí)現(xiàn)。
這樣的話前面介紹的KVO實(shí)現(xiàn)就好理解了
當(dāng)一個(gè)對象注冊了一個(gè)觀察者,被觀察對象的isa指針被修改的時(shí)候,isa指針就會指向一個(gè)中間類酌住,而不是真實(shí)的類店归。
所以isa指針其實(shí)不需要指向?qū)嵗龑ο笳鎸?shí)的類。所以我們的程序最好不要依賴于isa指針酪我。在調(diào)用類的方法的時(shí)候娱节,最好要明確對象實(shí)例的類名。
這樣只有當(dāng)我們調(diào)用KVC去訪問key值的時(shí)候KVO才會起作用祭示。所以肯定確定的是肄满,KVO是基于KVC實(shí)現(xiàn)的。
iOS開發(fā)筆記之基于鍵值的觀察者模式(KVO)
KVO簡而言之就是:基于鍵值的觀察者质涛,實(shí)際上就是觀察者模式稠歉。
Cocoa Framework已經(jīng)為我們提供了這一模式,不需要我們自己來實(shí)現(xiàn)了汇陆。我們只需要按照約定的方式去做就可以了怒炸。KVO主要用于用戶界面交互,當(dāng)多個(gè)View共同使用了同一個(gè)實(shí)體毡代,當(dāng)這個(gè)實(shí)體中的某個(gè)屬性改變時(shí)阅羹,如果需要更新多個(gè)界面,KVO的作用就發(fā)揮出來了教寂。
下面給出一個(gè)簡單的示例捏鱼,來展示如何使用KVO。
示例下載
有兩種方式可以在鍵值改變的時(shí)候給觀察者發(fā)送通知:自動方式和手動方式酪耕。其中自動方式是由NSObject提供的一個(gè)默認(rèn)實(shí)現(xiàn)导梆,通常情況下,如果你自定義了一個(gè)類是從NSObject繼承而來迂烁,那么該類就已經(jīng)具有了KVO的自動通知功能看尼,而且不需要額外的編寫代碼。如果需要手動控制通知方式盟步,那么需要重寫automaticallyNotifiesObserversForKey:方法藏斩。在該方法中如果需要手動控制通知方式,則將automaticallyNotifiesObserversForKey:返回NO却盘,否則返回YES狰域。
下面的例子是將openingBalance屬性設(shè)置為手動通知方式窜觉,其他屬性默認(rèn)為自動通知方式
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"openingBalance"])
{
automatic = NO;
}
else
{
automatic=[super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
手動通知方式的好處在于可以減少不必要的通知,比如你可以首先檢測一下該屬性值是否發(fā)生改變北专,如果發(fā)生改變則通知禀挫,否則不通知,代碼示例如下:
- (void)setOpeningBalance:(double)theBalance {
if (theBalance != openingBalance) {
[self willChangeValueForKey:@"openingBalance"];
openingBalance=theBalance;
[self didChangeValueForKey:@"openingBalance"];
}
}
如果一個(gè)單一的操作引發(fā)了多個(gè)屬性值的改變拓颓,那么就必須嵌套改變通知语婴。代碼示例如下:
- (void)setOpeningBalance:(double)theBalance {
[self willChangeValueForKey:@"openingBalance"];
[self willChangeValueForKey:@"itemChanged"];
openingBalance=theBalance;
itemChanged=itemChanged+1;
[self didChangeValueForKey:@"itemChanged"];
[self didChangeValueForKey:@"openingBalance"];
}