觀察者模式本質(zhì)上時(shí)一種發(fā)布-訂閱模型,用以消除具有不同行為的對(duì)象之間的耦合,通過這一模式,不同對(duì)象可以協(xié)同工作,同時(shí)它們也可以被復(fù)用于其他地方Observer從Subject訂閱通知,ConcreteObserver實(shí)現(xiàn)重現(xiàn)ObServer并將其重載其update方法。一旦SubJect的實(shí)例需要通知Observer任何新的變更,Subject會(huì)發(fā)送update消息來通知存儲(chǔ)在其內(nèi)部類中所注冊(cè)的Observer、在ConcreteObserver的update方法的實(shí)際實(shí)現(xiàn)中,Subject的內(nèi)部狀態(tài)可被取得并進(jìn)行后續(xù)處理。其類圖如下:

觀察者模式.png
由上面我們可以發(fā)現(xiàn)觀察者模式無非在是定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,并且當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變的時(shí)候,所有依賴于它的對(duì)象都會(huì)得到通知且自動(dòng)更新。即如果Subject允許其他觀察者(實(shí)現(xiàn)了觀察者接口的對(duì)象)對(duì)這個(gè)Subject的改變進(jìn)行請(qǐng)閱,當(dāng)Subject發(fā)送了變化,那么Subject會(huì)將這個(gè)變化發(fā)送給所有的觀察者,觀察者就能對(duì)Subject的變化做出更新。其時(shí)序圖如下

觀察者模式2.png
通過上面的觀察我們可以發(fā)現(xiàn)如果用N個(gè)Observer來拓展Subject的行為,這些Observer具有處理存儲(chǔ)在Subject中的信息的特定實(shí)現(xiàn),這樣也就實(shí)現(xiàn)了前面所說的消除不同對(duì)象間的耦合的功能了。
那么了解了這些我們可能就會(huì)更像了解下我們?cè)谑裁磿r(shí)候才會(huì)去使用觀察者模式呢?
- 當(dāng)需要將改變通知所有的對(duì)象時(shí),而你又不知道這些對(duì)象的具體類型
- 改變發(fā)生在同一個(gè)對(duì)象中,并需要改變其他對(duì)象將相關(guān)的狀態(tài)進(jìn)行更新且不知道有多少個(gè)對(duì)象。
而同樣的在我們?nèi)粘5拈_發(fā)中在Cocoa Touch框架中的的兩種經(jīng)常打交道的技術(shù)KVO與通知都實(shí)現(xiàn)了觀察者模式,所以下面我們討論的重點(diǎn)也就是基于這兩個(gè)方面的。
通知
小編發(fā)布了一篇文章,地址是:http://www.itdecent.cn/p/2efc71c4188a 里面有通知的基礎(chǔ)用法,這里就不過多的介紹了。
KVO
KVO是Key-Value-Observing的縮寫
通過KVO這種機(jī)制對(duì)象可以通過它得到其他對(duì)象的某個(gè)屬性的變更通知。這種機(jī)制在MVC模式下顯得更為重要,KVO可以讓視圖對(duì)象經(jīng)過控制器觀察模型對(duì)象的變更從而做出更新等操作。
KVO這一機(jī)制是基于NSKeyValueObserving協(xié)議的,Cocoa通過這個(gè)協(xié)議為所有遵循協(xié)議的對(duì)象提供了自動(dòng)觀察屬性變化的能力。在NSObject中已經(jīng)為我們實(shí)現(xiàn)了這一協(xié)議,所以我們不必去實(shí)現(xiàn)這個(gè)協(xié)議。
下圖形象的表示了KVO的一種工作流程:

為什么要使用KVO?
有的朋友可能會(huì)有疑問,為什么要使用KVO呢?KVO能實(shí)現(xiàn)的我使用Setter方法同樣能實(shí)現(xiàn)啊。其實(shí)不然KVO存在還是有它的價(jià)值的,那么接下來我們細(xì)數(shù)一下KVO的獨(dú)特價(jià)值吧:
1.我們創(chuàng)建一兩個(gè)setter方法感覺沒什么,但是如果要觀察的屬性非常多,那么還能一一重寫setter方法來實(shí)現(xiàn)嗎?想必大家心里已有了答案,但是利用KVO則能很好的解決上述問題。
2.我們自定義的類是很容易改寫setter方法的,但是如果你是用一個(gè)已經(jīng)編譯好了的類庫時(shí)要監(jiān)控其中一個(gè)屬性時(shí)怎么辦?難道還要去重寫setter方法?如果使用KVO則很輕松解決問題。
3.使用KVO能夠方便的記錄變化前的值和變化后的值,不適用KVO你還要自己來解決這些問題。
4.KVO讓你的代碼看起來更加簡(jiǎn)潔清晰易于維護(hù)。
觀察的是,屬性是否執(zhí)行了setter方法或者是是否使用了kvc賦值了,只要有賦值的動(dòng)作,都會(huì)執(zhí)行kvo的回調(diào)方法。如果賦值沒有通過setter方法或者KVC,例如(_name = @"新值"),這個(gè)時(shí)候,不會(huì)觸發(fā)kvo回調(diào)方法
一般KVO崩潰的原因
1.被觀察的對(duì)象銷毀掉了(被觀察的對(duì)象是一個(gè)局部變量)2.觀察者被釋放掉了,但是沒有移除監(jiān)聽 3.注冊(cè)的監(jiān)聽沒有移除掉,又從新注冊(cè)了一遍監(jiān)聽
KVO使用方法
添加觀察者和被觀察者
//你要觀察那個(gè)對(duì)象,就給那個(gè)對(duì)象添加一個(gè)觀察者
//觀察的是self.view 的backgroundColor屬性 獲取舊值和新值
//observer:觀察者(觀察self.view對(duì)象屬性的變化)
//keypath:被觀察屬性的名稱
//觀察屬性的新值,舊值等的一些配置(枚舉值)
//content:上下文,可以為kvo的回調(diào)方法傳值
//注冊(cè)觀察者(添加觀察者)
[self.view addObserver:self forKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
//觀察Person類的對(duì)象的name屬性
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
KVO回調(diào)方法
//keyPath:屬性名稱
//object:被觀察的對(duì)象
//change:變化前后的值都存儲(chǔ)在Change字典中
//context:注冊(cè)觀察者的時(shí)候content傳遞過來的值。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
//監(jiān)聽了好幾個(gè)屬性,使用keyPath區(qū)分
//監(jiān)聽了好幾個(gè)view,通過object判斷
//view的背景顏色發(fā)生了變化
if ([keyPath isEqualToString:@"backgroundColor"]) {
id oldColor = [change objectForKey:NSKeyValueChangeOldKey];
NSLog(@"舊值---%@---%@", NSKeyValueChangeOldKey, oldColor);
id newColor = [change objectForKey:NSKeyValueChangeOldKey];
NSLog(@"新值---%@---%@", NSKeyValueChangeNewKey, newColor);
}
//name屬性發(fā)生變化
if ([keyPath isEqualToString:@"name"]) {
id oldName = [change objectForKey:@"old"];
NSLog(@"oldName == %@", oldName);
id newName = [change objectForKey:@"new"];
NSLog(@"newName == %@", newName);
}
}
最后使用完之后釋放掉觀察者 一般在dealloc 或者是viewDidDisappear:方法中釋放掉觀察者
//在視圖已經(jīng)消失,移除監(jiān)聽
- (void)viewDidDisappear:(BOOL)animated {
//移除監(jiān)聽
[self.view removeObserver:self forKeyPath:@"name" context:nil];
}
//在ARC下,不能使用[super delloc]
- (void)dealloc {
//移除監(jiān)聽
[self.person removeObserver:self forKeyPath:@"name" context:nil];
self.person = nil;
}
參考文章:http://blog.csdn.net/xdrt81y/article/details/24039163 作者:LoveApp_Han
http://www.itdecent.cn/p/56d9417c3a04 作者:曉風(fēng)沐晨 http://www.itdecent.cn/users/350fee9655f0/latest_articles