iOS 提供了一種 “同步的” 消息通知機(jī)制NSNotificationCenter,觀察者只要向消息中心注冊(cè), 即可接受其他對(duì)象發(fā)送來的消息,消息發(fā)送者和消息接受者兩者可以互相一無所知,完全解耦。
消息機(jī)制常常用于在向服務(wù)器端請(qǐng)求數(shù)據(jù)或者提交數(shù)據(jù)的場(chǎng)景,在和服務(wù)器端成功交互后,需要處理服務(wù)器端返回的數(shù)據(jù),或發(fā)送響應(yīng)消息等,就需要用到消息機(jī)制
一、通知相關(guān)的類
-
NSNotification
這個(gè)類可以理解為一個(gè)消息對(duì)象,其中包含三個(gè)屬性
@property (readonly, copy) NSNotificationName name;
//消息對(duì)象的唯一標(biāo)識(shí),用于辨別消息對(duì)象。
@property (nullable, readonly, retain) id object;
//
@property (nullable, readonly, copy) NSDictionary *userInfo;
//用來傳遞消息
NSNotification的初始化方法:
<pre><code> - (instancetype)initWithName:(NSNotificationName)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo NS_AVAILABLE(10_6, 4_0) NS_DESIGNATED_INITIALIZER;</code></pre>
<pre><code>- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;</code></pre>
<pre><code>+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;</code></pre>
<pre><code>+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;</code></pre>
<pre><code>- (instancetype)init //不能調(diào)用 /NS_UNAVAILABLE/; /* do not invoke; not a valid initializer for this class */</code></pre>
注意:官方文檔有明確的說明,不可以使用init進(jìn)行初始化
-
NSNotificationCenter
這個(gè)類是通知中心,用單例設(shè)計(jì),每個(gè)應(yīng)用程序都會(huì)有一個(gè)默認(rèn)的通知中心,用于調(diào)度通知的發(fā)送和接受。
- 添加一個(gè)觀察者,可以為它指定一個(gè)方法,名字和對(duì)象。接收到通知時(shí),執(zhí)行方法。
<pre><code> - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;</code></pre>
參數(shù)解析:Observer:(誰來接受通知消息);selector(方法選擇,執(zhí)行哪個(gè)方法); name:(通知的名稱,也可以說是通知消息的標(biāo)識(shí),按照這個(gè)來區(qū)分是否接受通知);object:(接受誰的通知(進(jìn)行篩選),用這個(gè)參設(shè)置,nil為接受所有文件 發(fā)送的通知)。
- 發(fā)送通知消息的方法
<pre><code>- (void)postNotification:(NSNotification *)notification;</code></pre>
<pre><code>- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;</code></pre>
<pre><code>- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;</code></pre>
參數(shù)解析:Name:(通知標(biāo)識(shí),與注冊(cè)通知是的標(biāo)識(shí)對(duì)應(yīng));object:(發(fā)送方);userInfo:(重點(diǎn)說一下,這是一個(gè)字典類型的對(duì)象,可以把需要傳遞 的值放進(jìn)這個(gè)字典里)
- 移除通知的方法
<pre><code>- (void)removeObserver:(id)observer;</code></pre>
<pre><code>- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;</code></pre>
注意:1.如果發(fā)送的通知指定了object對(duì)象,那么觀察者接收的通知設(shè)置的object對(duì)象與其一樣,才會(huì)接收通知。但是接收通知如果將這個(gè)參數(shù)設(shè)置為nil則會(huì)接收一切通知。
2.觀察者的SEL函數(shù)指針可以有一個(gè)參數(shù),參數(shù)就是發(fā)送的消息對(duì)象本身,可以通過這個(gè)參數(shù)取到消息對(duì)象的usetInfo,實(shí)現(xiàn)傳值。
二、通知的使用流程
要想完成一個(gè)通知,主要有分三個(gè)執(zhí)行步驟,而且是按順序來的
順序很重要,順序很重要,順序很重要,重要的事情說三遍
1.要保證注冊(cè)通知在發(fā)送消息之前
- 首先,在需要接收通知的地方注冊(cè)觀察者,比如
<pre>
//獲取通知中心單例對(duì)象
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
//添加當(dāng)前類對(duì)象為一個(gè)觀察者,name和object設(shè)置為nil,表示接收一切通知
[center addObserver:self selector:@selector(notice:) name:@"aha" object:nil];
</pre> - 然后,在我們需要時(shí)發(fā)送通知消息
<pre>
//創(chuàng)建消息對(duì)象
NSNotification *notification = [[NSNotification alloc]initWithName:@"aha" object:@"hah" userInfo:@{@"我是消息":@"hello"}];
//使用通知中心發(fā)送通知
[[NSNotificationCenter defaultCenter]postNotification:notification];</pre> - 最后移除通知
<pre>
//移除通知
[[NSNotificationCenter defaultCenter]removeObserver:self name:@"aha" object:nil];
</pre>
三、通知知識(shí)注意事項(xiàng)及拓展
通知一般用在反向傳值,如果要實(shí)現(xiàn)正向傳值則必須保證:注冊(cè)通知在發(fā)送通知之前實(shí)現(xiàn)
可以參考:Mazy_ma的博客:iOS-通知正向傳值問題多個(gè)監(jiān)聽者監(jiān)聽同一個(gè)消息時(shí):
監(jiān)聽同一條通知的多個(gè)觀察者,在通知到達(dá)時(shí),它們執(zhí)行回調(diào)的順序是不確定的,所以我們不能去假設(shè)操作的執(zhí)行會(huì)按照添加觀察者的順序來執(zhí)行關(guān)于注冊(cè)監(jiān)聽者,還有一個(gè)需要注意的問題是,每次調(diào)用addObserver時(shí),都會(huì)在通知中心重新注冊(cè)一次,即使是同一對(duì)象監(jiān)聽同一個(gè)消息,而不是去覆蓋原來的監(jiān)聽。這樣,當(dāng)通知中心轉(zhuǎn)發(fā)某一消息時(shí),如果同一對(duì)象多次注冊(cè)了這個(gè)通知的觀察者,則會(huì)收到多個(gè)通知。
-
默認(rèn)的通知是同步的:
代碼如下:
<pre>
-(void)viewDidLoad {
[super viewDidLoad];
//注冊(cè)通知觀察者
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(comeTrueNotification:) name:kNotificationName object:@"wow"];
UIButton *sendNoticeBtn = [UIButton buttonWithType:UIButtonTypeSystem];
[sendNoticeBtn setFrame:CGRectMake(100, 100, 100, 100)];
[sendNoticeBtn setBackgroundColor:[UIColor cyanColor]];
[sendNoticeBtn setTitle:@"sendBtn" forState:UIControlStateNormal];
[self.view addSubview:sendNoticeBtn];
[sendNoticeBtn addTarget:self action:@selector(sendNoticeBtnDown) forControlEvents:UIControlEventTouchUpInside];NSLog(@"%d,%@,viewDidLoad",__LINE__,[NSThread currentThread]);
}
-(void)comeTrueNotification:(NSNotification *)notification {
NSString *message = notification.object;
//執(zhí)行消息方法
NSLog(@"%d,%@,%@",LINE,[NSThread currentThread],message);
sleep(5);
NSLog(@"%d,%@,comeTrueNotification",__LINE__,[NSThread mainThread]);
}
-(void)sendNoticeBtnDown {
//發(fā)送消息
[[NSNotificationCenter defaultCenter]postNotificationName:kNotificationName object:@"wow"];
NSLog(@"%d,%@,sendNoticeBtnDown",__LINE__,[NSThread currentThread]);
}
</pre>
運(yùn)行結(jié)果:
可以參考好文:NSNotificationCenter的同步和異步
- 通知異步的處理方法:(參考:NSNotificationCenter的同步和異步)
- 通知異步方法一:
讓通知事件處理方法在子線程中執(zhí)行:(例如:)
<pre>
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(5);
});
</pre> - 通知異步方法二:
您可以通過NSNotificationQueue的enqueueNotification:postingStyle:和enqueueNotification:postingStyle:coalesceMask:forModes:方法將通告放入隊(duì)列,實(shí)現(xiàn)異步發(fā)送,在把通告放入隊(duì)列之后,這些方法會(huì)立即將控制權(quán)返回給調(diào)用對(duì)象。
我們修改sendNoticeBtnDown事件如下:
<pre>
//發(fā)送消息
NSNotification *notification = [NSNotification notificationWithName:kNotificationName object:@"wow"];
[[NSNotificationQueue defaultQueue]enqueueNotification:notification postingStyle:NSPostASAP];
// [[NSNotificationCenter defaultCenter]postNotificationName:kNotificationName object:@"wow"];
</pre>通過通知隊(duì)列來管理通知,不會(huì)再造成阻塞
- 關(guān)于通知的移除
雖然在 IOS 用上 ARC 后,不顯示移除 NSNotification Observer 也不會(huì)出錯(cuò),但是這是一個(gè)很不好的習(xí)慣,不利于性能和內(nèi)存。
注銷觀察者有2個(gè)方法:
a. 最優(yōu)的方法,在 UIViewController.m 中:
<pre>-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}</pre>
b. 單個(gè)移除:
<pre>
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"Notification_GetUserProfileSuccess" object:nil];
</pre>注意:
removeObserver:
是刪除通知中心保存的調(diào)度表一個(gè)觀察者的所有入口,而removeObserver:name:object:
是刪除匹配了通知中心保存的調(diào)度表中觀察者的一個(gè)入口。
這個(gè)比較簡(jiǎn)單,直接調(diào)用該方法就行。例如:
[[NSNotificationCenter defaultCenter] removeObserver:observer name:nil object:self];
注意參數(shù)notificationObserver
為要?jiǎng)h除的觀察者,一定不能置為nil。