iOS 趣談設計模式——通知
【前言介紹】
iOS的一種設計模式,觀察者Observer模式(也叫發布/訂閱,即Publich/Subscribe模式)。
觀察者模式,包含了通知機制(notification)和KVO(Key-value-observing)機制。
在這本文中,我們將介紹在日常項目當中經常使用到的通知機制這一種設計模式。
通知機制:
委托機制是代理“一對一”的對象之間的通信,而通知機制是廣播“一對多”的對象之間的通信;
一、是什么?【生活問題例子】
“短信天氣預報”
當A類發送一條信息給通知中心時,注冊為用戶(觀察者)的B類群就會收到相應的通知,并作出反應。
二、有什么用?【代碼中的應用】
在不同類之間如何傳遞數據?
有幾種方法:屬性傳遞、代理協議,另外就是通知。
通知:在A類中創建的方法,B類中執行,且可以使用該通知攜帶數據傳遞給對方;
三、有什么不同?【與其他“通知”的不同?】
經常提到的通知,有“廣播通知”、“本地通知”、“推送通知”
本文所介紹的就是廣播通知,是實現觀察者模式的一種機制,可以在一個應用中的多個對象之間進行通信傳遞數據。
而本地通知和推送通知主要是給用戶發送“通知提示”,例如警告提示、聲音、震動以及如圖標上的紅色數字提示。
第一種由“本地發送通知”給用戶,第二種由第三方應用發送給蘋果官方的遠程服務器,然后再由服務器“推送通知”給用戶。
四、產品經理:老規矩,代碼拿來~【具體實現】
過程:
在通知機制中,需要(或者說感興趣)接收某個通知的信息的所有對象都可以成為接收者,首先注冊成為觀察者。
進行注冊后,通知中心就會把發布者發送的通知信息,廣播給注冊過該通知的觀察者。且觀察者只能接收到通知中心的信息,不能知道通知是誰投送的。
最后,接受者不想再對關注該通知的信息時,可以給通知中心發生解除注冊的信息,之后都不再接收到通知了。
1.獲取通知中心(NSNotificationCenter)對象:(就像獲取移動營運商短信中心的權限,作為媒介)
發布、注冊、解除通知都需要使用通知中心,負責協助不同對象、不同類之間的消息通信。
[NSNotificationCenter defaultCenter]; //需要注意的是,通知中心也是一個單例
2.發布(A類)和接收(B類)
a.做為發布者的A類發送通知
可以使用一下三個方法:
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSString *)aNameobject:(id)anObject;
- (void)postNotificationName:(NSString )aNameobject:(id)anObject userInfo:
(NSDictionary )aUserInfo;</code></pre>
- postNotificationName:指定消息名稱;
- object:指定發消息者;
- userInfo:通知中用于傳遞參數的載體,傳遞的方法是把參數放在NSDictionary類型的userInfo中。例如:NSDictionary *dict = [notification userInfo];
一般使用第三個方法,如果參數不需要的,可以設置為nil.
b.注冊通知,加入觀察者:
做為觀察者B類注冊通知,進行監聽:
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;
//@selector中為回調方法,在本類中對通知進行相應的處理,name為通知名稱、object為對象;
剖析:
- object==nil,那么客戶對象(self)將收到任何對象發出NSWindowDidBecomeMainNotification的通知消息;
- name==nil,那么觀察者將接收到object對象的所有消息,但是確定不了接收這些消息的順序。
- object==nil,name==nil,那么該觀察者將收到所有對象的所有消息。
對于一個任意的觀察者observer,如果不能保證其對應的selector有本類自定義的方法:(例如,MyMethod),可采用[observer respondsToSelector:@selector(MyMethod:)]]進行檢查。
所以完整的添加觀察者過程為:
if([observer respondsToSelector:@selector(MyMethod:)]) {
[[NSNotificationCenter defaultCenter] addObserver:observer selector:
@selector(MyMethod:) name:NSWindowDidBecomeMainNotification object:nil];
}
當然在蘋果API中也有另外一個注冊觀察者的方法:
- (id )addObserverForName:(nullable NSString *)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block
此方法是支持在該方法中進行block回調的,而queue參數就是表示此模塊在queue隊列中進行。
但是這方法一般不采用,所以建議使用第一種方法進行觀察者的創建。
c.移除通知
由于通知中心不會retain觀察者對象,因此注冊過的對象必須在釋放之前注銷掉,如果不這樣的話,當該通知再次出現時,通知中心會向已釋放的觀察者對象發送消息,從而導致應用崩潰。
在ARC下,系統會自動回收無用的通知對象內存,但是由于系統回收機制ARC有一定的延遲性,所以即使不會出錯,也建議養成習慣,對通知進行手動釋放無用的通知。
移除有2種方法:
//釋放所有通知
- (void)removeObserver:(id)observer;
//釋放名稱為aName的通知
- (void)removeObserver:(id)observer name:(nullable NSString )aName object:(nullable id)anObject;</code></pre>
一般在視圖控制器中,可以在“didReceiveMemoryWarning:”中發送解除消息:
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
//移除觀察者
[[NSNotificationCenter defaultCenter]removeObserver:self];
}
五、那些年我們用過的系統通知名稱~
系統自帶的也有許多有用的通知,我們只需要注冊為相應的通知接收對象,就能根據通知狀態的變化發生相應的數據改變。
部分系統通知名稱如下:
UIApplicationDidFinishLaunchingNotification // 應用程序啟動后
UIApplicationDidBecomActiveNotification //進入前臺
UIApplicationWillResignActiveNotification //應用將要進入后臺
UIApplicationDidEnterBackgroundNotification //進入后臺
UIKeyboardWillShowNotification // 鍵盤即將顯示
UIKeyboardDidShowNotification // 鍵盤顯示完畢
UIKeyboardWillHideNotification // 鍵盤即將隱藏
UIKeyboardDidHideNotification // 鍵盤隱藏完畢
六、舉個栗子
本文有2個例子:
- 一個是完整的通知發布、接收、解除過程;
- 一個是系統通知名稱的應用(以第三個:UIApplicationWillResignActiveNotification
為例);
(by:覺得文章太長不想看這段的童鞋,也可以到github上下載啊左的demo,:Mydemo1、Mydemo2。自己琢磨琢磨。
點擊“DownLoad ZIP”按鈕就可以了。一般使用Safari瀏覽器下載得了,啊左用QQ瀏覽器貌似下載不了...囧)
【本次開發環境: Xcode:7.2 iOS Simulator:iphone6 By:啊左】
1.完整的通知發布、接收、解除過程:
UI控件擺放如下,視圖、控件的背景可以自己設置成比較明顯的顏色,便于觀察:


- A視圖創建一個textView用于顯示B視圖傳遞過來的信息,一個按鈕用于切換到B視圖;
- B視圖創建一個文本框用于更新信息,一個按鈕用于把文本框的信息更新并返回到視圖A。
然后,點擊A類的按鈕,并且按住control拖拽到B視圖的控制器后松開鼠標,在彈出的選擇框(如下圖)選擇:“Present Modally”用于創建A、B控制器之間的模態類型的Segue。

接下來,我們需要在新建一個視圖控制器B類SeocndViewController:

回到故事板中,選擇B視圖控制器,打開標識檢查器(下圖第一排第三個選項),選擇class為:SeocndViewController。這就使代碼與故事板中的視圖控制器對應起來。(A視圖默認對應ViewController,如果有錯誤,可以檢查一下。)

然后我們打開輔助編輯器,按住control,拖拽A視圖中的文本連接到對應的輸出口,這里我們命名為“myLabel”.

以此方式,繼續為B類中的文本框連接到代碼中,并命名為:“MyTextView”,
為B類的按鈕添加行為,方法名為:“saveBtn:”,
啊左還是覺得上代碼實在點:
(ViewController.h類)
#import
@interface ViewController : UIViewController
//每次視圖打開后,監聽B類的數據是否發生變化,如有變化,在這個文本視圖中顯示更新。
@property (weak, nonatomic) IBOutlet UITextView
myLabel;
@end</code></pre>
(ViewController.m類)
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//1.注冊為觀察者,監聽B視圖中的通知
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(AMethod:) name:@"MyNotificationName" object:nil];
}
//回調方法:AMethod:
-(void)AMethod:(NSNotification )notification
{
//2.獲取通知攜帶的數據,更新label的文本信息
NSDictionary dictData = [notification userInfo];
NSString *str = [dictData objectForKey:@"MyUserInfoKey"];
self.myLabel.text = str;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
//3.移除所有通知
[[NSNotificationCenter defaultCenter]removeObserver:self];
}
@end</code></pre>
(SecondViewController.h)
#import
@interface SecondViewController : UIViewController
//文本框,用于更新傳遞給ViewController視圖的數據
@property (weak, nonatomic) IBOutlet UITextField *MyTextView;
(IBAction)saveBtn:(UIButton *)sender; //保存返回按鈕事件
@end</code></pre>
(SecondViewController.m)
#import "SecondViewController.h"
@interface SecondViewController ()
@end
@implementation SecondViewController
(void)viewDidLoad {
[super viewDidLoad];
}
(IBAction)saveBtn:(UIButton *)sender {
//返回視圖A并在塊中發布通知
[self dismissViewControllerAnimated:YES completion:^{
//1.創建userInfo攜帶的信息
NSString str = self.MyTextView.text;
NSDictionary dictData = [NSDictionary dictionaryWithObject:str forKey:@"MyUserInfoKey"];
//2.發布信息
[[NSNotificationCenter defaultCenter]postNotificationName:@"MyNotificationName" object:nil userInfo:dictData];
}];
}
@end</code></pre>
驗證:
第一次A視圖的文本視圖中沒有顯示數據,點擊按鈕“確定切換頁面”,打開視圖B,在文本框中輸入信息(例如123),點擊“保存返回”按鈕,在A視圖的文本視圖中看到更新的信息:123。
by:有需要的童鞋可以到github上下載啊左的demo:Mydemo1。
2.系統通知名稱的應用(以UIApplicationWillResignActiveNotification為例):
UIApplicationWillResignActiveNotification的意思是應用即將進入后臺的這個時刻。
首先,創建UI界面如下,相比第一個例子,這個會簡單很多:一個按鈕+一個顯示顏色的UIView視圖。

創建一個命名為“myView”的UIView控件,一個方法為“changeColorBtn:”的按鈕行為即可,關聯ViewController控制器。
代碼如下:
(ViewController.h類)
#import
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UIView *myView;
(IBAction)changeColorBtn:(UIButton *)sender;
@end</code></pre>
(ViewController.m類)
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
(void)viewDidLoad {
[super viewDidLoad];
//1. 注冊為觀察者
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(applicationWillResignActiveNotification:) name:UIApplicationWillResignActiveNotification object:nil];
}
//2.當應用即將進入后臺時,調用通知回調方法:
-(void)applicationWillResignActiveNotification:(NSNotification *)notification{
//返回后臺的過程,把視圖背景改為紅色;
self.myView.backgroundColor = [UIColor redColor];
}
- (IBAction)changeColorBtn:(UIButton *)sender {
//按鈕把視圖背景改為黃色;
self.myView.backgroundColor = [UIColor yellowColor];
}
@end</code></pre> 視圖第一次打開,視圖為默認白色:
點擊按鈕,視圖變為黃色:

>【按鈕事件】
按住“command+shift”,雙擊H,進入iOS多任務欄;
或者按住“command+shift”,單擊H,回到模擬器主界面。

>【iOS多任務欄】
發現,以上2種情況都可以看到視圖變為紅色。
且回到應用后,顏色仍然是紅色。

>【回到前臺】
也就是,當應用從活躍的狀態進入非活躍狀態的時候,系統自動發送“UIApplicationWillResignActiveNotification”這個通知,如有注冊監聽者(觀察者),則執行回調方法。
by:有需要的童鞋可以到github上下載啊左的demo:Mydemo2。
文/啊左(簡書)