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 *)aName object:(id)anObject;

- (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
  • 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 == nilname == 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 <NSObject>)addObserverForName:(nullable NSString *)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block

此方法是支持在該方法中進行block回調的,而queue參數就是表示此模塊在queue隊列中進行。

但是這方法一般不采用,因為在此方法的 block 中,稍微不注意調用 self 的話,會引起循環引用,造成內存泄露,所以還是建議使用第一種方法進行觀察者的創建。 

c.移除通知

注冊過的對象必須在釋放之前注銷掉,如果不這樣的話,當該通知再次出現時,通知中心可能會向已釋放的觀察者對象發送消息,從而導致應用崩潰。

在ARC下,系統會自動回收無用的通知對象內存,但是由於系統回收機制ARC有一定的延遲性,所以即使不會出錯,也建議養成習慣,對通知進行手動釋放無用的通知。
移除有2種方法:

//釋放所有通知
- (void)removeObserver:(id)observer;
//釋放名稱為aName的通知 - (void)removeObserver:(id)observer name:(nullable NSString *)aName object:(nullable id)anObject;

 一般在視圖控制器中,可以在“didReceiveMemoryWarning:”中發送解除消息:【這只是參考,建議還是在 :-(void)dealloc ){} 中進行移除。 】

-(void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    //移除觀察者
    [[NSNotificationCenter defaultCenter]removeObserver:self];
}

(by:從 iOS 9 開始通知中心會對觀察者進行 weak 弱引用,所以不需要在觀察者對象釋放之前從通知中心移除。即使不對通知進行手動移除,指針也會在注冊者被回收后自動置空,向空指針 nil 發送消息是不會有問題的。
但是,通過 - (id )addObserverForName: object: queue: usingBlock: 方法注冊的觀察者依然需要手動的釋放,因為通知中心對它們持有的是強引用。)

 

五、那些年我們用過的系統通知名稱~

系統自帶的也有許多有用的通知,我們只需要注冊為相應的通知接收對象,就能根據通知狀態的變化發生相應的數據改變。

部分系統通知名稱如下:

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 <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (weak, nonatomic) IBOutlet UITextView *myLabel;   //每次視圖打開后,監聽B類的數據是否發生變化,如有變化,在這個文本視圖中顯示更新。

@end

(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];
}

-(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

 

(SecondViewController.h)

#import <UIKit/UIKit.h>

@interface SecondViewController : UIViewController

@property (weak, nonatomic) IBOutlet UITextField *MyTextView;  //文本框,用於更新傳遞給ViewController視圖的數據

- (IBAction)saveBtn:(UIButton *)sender;                        //保存返回按鈕事件

@end

(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

驗證:

第一次A視圖的文本視圖中沒有顯示數據,點擊按鈕“確定切換頁面”,打開視圖B,在文本框中輸入信息(例如123),點擊“保存返回”按鈕,在A視圖的文本視圖中看到更新的信息:123。

by:有需要的童鞋可以到github上下載啊左的demo:Mydemo1

 

2.系統通知名稱的應用(以UIApplicationWillResignActiveNotification為例):

UIApplicationWillResignActiveNotification的意思是應用即將進入后台的這個時刻。

首先,創建UI界面如下,相比第一個例子,這個會簡單很多:一個按鈕+一個顯示顏色的UIView視圖。

 

創建一個命名為“myView”的UIView控件,一個方法為“changeColorBtn:”的按鈕行為即可,關聯ViewController控制器。

代碼如下:

(ViewController.h)

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (weak, nonatomic) IBOutlet UIView *myView;
- (IBAction)changeColorBtn:(UIButton *)sender;

@end

(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

視圖第一次打開,視圖為默認白色:

點擊按鈕,視圖變為黃色:

按住“command+shift”,雙擊H,進入iOS多任務欄;

或者按住“command+shift”,單擊H,回到模擬器主界面。

都可以看到視圖變為紅色。

且回到應用后,顏色仍然是紅色。

表示,當應用從活躍的狀態進入非活躍狀態的時候,系統自動發送“UIApplicationWillResignActiveNotification”這個通知,如有注冊監聽者(觀察者),則執行回調方法。

by:有需要的童鞋可以到github上下載啊左的demo:Mydemo2

 

 


免責聲明!

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



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