Notification Center的概念:
它是一個單例對象,允許當事件發生時通知一些對象,讓對象做出相應反應。
它允許我們在低程度耦合的情況下,滿足控制器與一個任意的對象進行通信的目的。 這種模式的基本特征是為了讓其他的對象能夠接收到某種事件傳遞過來的通知,主要使用通知名稱來發送和接收通知。
基本上不用考慮其它影響因素,只需要使用同樣的通知名稱,監聽該通知的對象(即觀察者)再對通知做出反應即可。
優勢:
1.不需要編寫多少代碼,實現比較簡單;
2.對于一個發出的通知,多個對象能夠做出反應,簡單實現1對多的方式,較之于 Delegate 可以實現更大的跨度的通信機制;
3.能夠傳遞參數(object和userInfo),object和userInfo可以攜帶發送通知時傳遞的信息。
缺點:
1.在編譯期間不會檢查通知是否能夠被觀察者正確的處理;
2.在釋放通知的觀察者時,需要在通知中心移除觀察者;
3.在調試的時候,通知傳遞的過程很難控制和跟蹤;
4.發送通知和接收通知時需要提前知道通知名稱,如果通知名稱不一致,會出現不同步的情況;
5.通知發出后,不能從觀察者獲得任何的反饋信息。
<一>NSNotification 一般使用情況的的5種使用方式
1、不傳遞參數, 最常用的一種
/*----------------------- 一 --------- 不傳遞參數, 最常用的一種 ------------------------------------*/
// 第一步:監聽通知
- (void)ListeningToNotification1{
//監聽
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noti1) name:@"noti1" object:nil];
}
// 第二部:發送通知
-(void)btn1Click{
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti1" object:nil];
}
//調用方法
-(void)noti1{
NSLog(@"接收 不帶參數的消息");
}
2、 使用監聽一方的調用方法時 傳遞消息
//發通知
/*--------------- 二 ------ 使用監聽一方的調用方法時 傳遞消息-------------*/
// 第一步:監聽通知
- (void)ListeningToNotification2{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noti2:) name:@"noti2" object:nil];
}
// 第二部:發送通知(可以跨控制器進行發送)
-(void)btn2Click:(UIButton *)btn
{
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti2" object:[NSString stringWithFormat:@"%@",btn.titleLabel.text]];
}
//調用方法
-(void)noti2:(NSNotification *)noti
{
//使用object處理消息
NSString *info = [noti object];
NSLog(@"接收 object傳遞的消息:%@",info);
}
3、使用發送通知一方的 userInfo 傳遞消息
/*----------- 三 ----------- 使用發送通知一方的userInfo 傳遞消息 ----------*/
// 第一步:監聽通知
-(void)ListeningToNotification3{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noti3:) name:@"noti3" object:nil];
}
//第二步:發送通知
-(void)btn3Click{
NSDictionary *dic = [NSDictionary dictionaryWithObject:@"wangLei在這里" forKey:@"param"];
// 我把方法的object都設為了nil。那么這個參數起到什么作用呢?這個參數有點像二次確認的意思,就是在同一個通知name的情況下還可以通過object再次進行細分通知。就拿上面這個小demo說,如果object為空,接收方會接受所有名字為giveName的通知。但是如果object不為空,接收方就會只接收名字為giveName的而且object正確的通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti3" object:nil userInfo:dic];
}
//調用方法
-(void)noti3:(NSNotification *)noti
{
//使用userInfo處理消息
NSDictionary *dic = [noti userInfo];
NSString *info = [dic objectForKey:@"param"];
NSLog(@"接收 userInfo傳遞的消息:%@",info);
}
// 最后一步:移除通知
-(void)dealloc
{
//移除觀察者,Observer不能為nil
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
4 :使用block接收通知,不用再指定方法了
//第一步:接收通知
- (void)ListeningToNotification4{
[[NSNotificationCenter defaultCenter]addObserverForName:@"noti4" object:nil queue:nil usingBlock:^(NSNotification *not){
//使用userInfo處理消息
NSDictionary *dic = [not userInfo];
NSString *info = [dic objectForKey:@"param"];
NSLog(@"使用block接收傳遞的消息:%@",info);
}];
}
//第二步:發送通知
-(void)btn4Click{
NSDictionary *dic = [NSDictionary dictionaryWithObject:@"wangLei" forKey:@"param"];
// 我把方法的object都設為了nil。那么這個參數起到什么作用呢?這個參數有點像二次確認的意思,就是在同一個通知name的情況下還可以通過object再次進行細分通知。就拿上面這個小demo說,如果object為空,接收方會接受所有名字為giveName的通知。但是如果object不為空,接收方就會只接收名字為giveName的而且object正確的通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti4" object:nil userInfo:dic];
}
5、使用NSNotificationQueue實現通知的異步
// 第一步:注冊通知
- (void)ListeningToNotification5{
/**
* 注冊一個通知
*
*/
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getNotification:) name:@"notificationMethon" object:nil];
}
//第二部: 發出通知
- (void)btn5Click{
// [[NSNotificationCenter defaultCenter] postNotificationName:@"notificationMethon" object:nil userInfo:@{@"key":@"value"}];
NSNotification *notifacation = [[NSNotification alloc]initWithName:@"notificationMethon" object:nil userInfo:@{@"key":@"value1"}];
[[NSNotificationQueue defaultQueue] enqueueNotification:notifacation postingStyle:NSPostNow];
/*
我們可以通過,NSNotificationQueue的defaultQueue來獲取到這個通知隊列,然后調用enqueueNotification來發出通知,我們可以看到第二個參數postingStyle,這個參數是一個枚舉,他可以是以下三個值:
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1,
NSPostASAP = 2,
NSPostNow = 3
};
這三個不同的值是有一定區別的。(以下內容摘抄自網絡)
盡快發送
以NSPostASAP風格進入隊列的通告會在運行循環的當前迭代完成時被發送給通
告中心,如果當前運行循環模式和請求的模式相匹配的話(如果請求的模式和當
前模式不同,則通告在進入請求的模式時被發出)。由于運行循環在每個迭代過
程中可能進行多個調用分支(callout),所以在當前調用分支退出及控制權返回
運行循環時,通告可能被分發,也可能不被分發。其它的調用分支可能先發生
,比如定時器或由其它源觸發了事件,或者其它異步的通告被分發了。
您通常可以將NSPostASAP風格用于開銷昂貴的資源,比如顯示服務器。如果在
運行循環的一個調用分支過程中有很多客戶代碼在窗口緩沖區中進行描畫,在每
次描畫之后將緩沖區的內容刷新到顯示服務器的開銷是很昂貴的。在這種情況
下,每個draw...方法都會將諸如“FlushTheServer” 這樣的通告排入隊列,并指定
按名稱和對象進行聚結,以及使用NSPostASAP風格。結果,在運行循環的最
后,那些通告中只有一個被派發,而窗口緩沖區也只被刷新一次。
空閑時發送
以NSPostWhenIdle風格進入隊列的通告只在運行循環處于等待狀態時才被發
出。在這種狀態下,運行循環的輸入通道中沒有任何事件,包括定時器和異步事
件。以NSPostWhenIdle風格進入隊列的一個典型的例子是當用戶鍵入文本、而
程序的其它地方需要顯示文本字節長度的時候。在用戶輸入每一個字符后都對文
本輸入框的尺寸進行更新的開銷是很大的(而且不是特別有用),特別是當用戶
快速輸入的時候。在這種情況下,Cocoa會在每個字符鍵入之后,將諸
如“ChangeTheDisplayedSize”這樣的通告進行排隊,同時把聚結開關打開,并使
用NSPostWhenIdle風格。當用戶停止輸入的時候,隊列中只有一
個“ChangeTheDisplayedSize”通告(由于聚結的原因)會在運行循環進入等待狀
態時被發出,顯示部分也因此被刷新。請注意,運行循環即將退出(當所有的輸
入通道都過時的時候,會發生這種情況)時并不處于等待狀態,因此也不會發出
通告。
立即發送
以NSPostNow風格進入隊列的通告會在聚結之后,立即發送到通告中心。您可以
在不需要異步調用行為的時候 使用NSPostNow風格(或者通過
NSNotificationCenter的postNotification:方法來發送)。在很多編程環境下,我
們不僅允許同步的行為,而且希望使用這種行為:即您希望通告中心在通告派發
之后返回,以便確定觀察者對象收到通告并進行了處理。當然,當您希望通過聚
結移除隊列中類似的通告時,應該用enqueueNotification...方法,且使用
NSPostNow風格,而不是使用postNotification:方法。
*/
}
//
-(void)getNotification:(NSNotification *)info{
NSDictionary *dict = info.userInfo;
NSLog(@"當前執行任務的線程為%@",[NSThread currentThread]);
}
// 第三部:我們將調用發出通知的代碼放在一個非主隊列里面
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
[self btn5Click];
[NSThread sleepForTimeInterval:3];
NSLog(@"1目前的queue所在的線程為%@",[NSThread currentThread]);
}];
運行結果如下:
2017-02-20 16:14:53.217 NSNotificationDemo[6438:492325] 這是通知后面的代碼,按照通知是同步的道理,應該在通知完成回調之后才會執行
2017-02-20 16:14:53.219 NSNotificationDemo[6438:493599] 這是通知里面執行的代碼,這里的代碼正常情況下要執行完之后,才能執行通知之后的代碼;當前執行任務的線程為<NSThread: 0x600000077880>{number = 3, name = (null)}
2017-02-20 16:14:56.222 NSNotificationDemo[6438:493599] 1目前的queue所在的線程為<NSThread: 0x600000077880>{number = 3, name = (null)}
記住下面的話:
上面說到 NSNotificationCenter是一個同步操作,也就是只有當響應的通知的代碼執行完畢以后,發出通知的對象的代碼才會繼續往下執行。
那么 NSNotificationQueue就有一些區別,他有兩個非常重要的特點:即通告的聚結和異步發送。聚結是把和剛進入隊列的通告相類似的其它通告從隊列中移除的過程。如果一個新的通告和已經在隊列中的通告相類似,則新的通告不進入隊列,而所有類似的通告(除了隊列中的第一個通告以外)都被移除。然而,您不應該依賴于這個特殊的聚結行為。
而異步發送則很好理解了,也就是說發出通知以后立刻返回,也就是是繼續執行下面的代碼,并不管通知發出后的具體情況
總結NSNotificationCenter和NSNotificationQueue的區別,也許最大的一點就是發出通知時一個是同步一個是異步。
最后,記得在發送通知消息的頁面,在dealloc方法里面移除觀察者。
-(void)dealloc
{
//移除觀察者,Observer不能為nil
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
<二>NSNotification 添加本地通知的使用方式
demo2下載地址
<1>、本地通知欄通知
1.1、本地通知的創建和使用
本地通知其實在之前的文章和網上就已經很詳細的說了,主要是UILocalNotification的使用,本地通知主要應用在固定時間的通知事件,比如日歷、活動提醒等
NSLog(@"添加本地通知");
//ios8.0以上的系統需要注冊通知
if (D_ISHight(8.0)) {
//注冊通知
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeSound|UIUserNotificationTypeBadge categories:nil]];
}
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
//設置固定時間通知
if (sender.tag == LOCALNOTIFICATION1) {
NSDateFormatter * forma = [[NSDateFormatter alloc] init];
[forma setDateFormat:@"HH:mm"];
localNotification.fireDate = [forma dateFromString:@"13:20"]; //12點提醒
[localNotification setRepeatInterval:NSCalendarUnitDay]; //每天12點
}
//設置重復間隔時間
// else if (sender.tag == LOCALNOTIFICATION1){
// NSDate *date =[[NSDate alloc] init];
// localNotification.fireDate = [date dateByAddingTimeInterval:5];//重復間隔的時長
// [localNotification setRepeatInterval:NSCalendarUnitSecond];//時間間隔單位,秒
// }
[localNotification setTimeZone:[NSTimeZone defaultTimeZone]]; //時區
//ios8.2以上的系統可以設置標題
if (D_ISHight(8.2)) {
[localNotification setAlertTitle:@"提醒標題"];
}
[localNotification setAlertBody:@"提醒內容:Damon"];
[localNotification setAlertAction:@"鎖屏時顯示的動作標題"]; //在鎖屏時顯示的動作標題(完整測標題:"滑動來" + alertAction)
[localNotification setApplicationIconBadgeNumber:1]; //設置提醒的軟件右上角的小紅點
[localNotification setSoundName:UILocalNotificationDefaultSoundName];//默認聲音
//或者指定文件名localNotification.soundName = @"123.wav";
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification]; //調用通知
在本地消息調用之后,就會在通知欄顯示了,點擊通知欄的消息之后,會自動打開這個軟件,需要注意的是軟件不同的狀態,點擊通知欄調用的函數也不同。
1、軟件如果是在后臺運行,并沒有退出,調用的是AppDelegate的這個函數
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
2、軟件已經主動退出,或者后臺超過時間退出,點擊通知欄消息之后就只會調用這個函數
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
3、所以不同的狀態,調用的函數也不同,那么想獲取是哪個通知調用的函數方法也不相同了,如果是后臺運行狀態下,想知道某個通知,可以通過不同的userInfo這樣實現
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
NSLog(@"Application did receive local notifications");
// 在這里寫跳轉代碼
// 如果是應用程序在前臺,依然會收到通知,但是收到通知之后不應該跳轉
if (application.applicationState == UIApplicationStateActive)
{
return;
}
if (application.applicationState == UIApplicationStateInactive) {
// 當應用在后臺收到本地通知時執行的跳轉代碼
//可以通過設置通知時的userinfo過濾某條通知
if ([notification.userInfo[@"name"] isEqualToString:@"Damon"]) {
NSLog(@"damon");
}
//可以得到所有的通知
for (UILocalNotification *noti in [[UIApplication sharedApplication] scheduledLocalNotifications]) {
NSLog(@"%@",noti.fireDate);
}
//通知之后可以取消對應的通知
[[UIApplication sharedApplication] cancelLocalNotification:notification];
}
[self jump:1];
}
4、而如果app已經退出,那么在啟動函數里面獲取通知就需要通過launchOptions里面的UIApplicationLaunchOptionsLocalNotificationKey來實現,比如這樣:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//當應用退出之后,點擊通知跳轉到應用會走這個函數,而不是didReceiveLocalNotification
UILocalNotification * local = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if ([local.userInfo[@"name"] isEqualToString:@"Damon"]) {
NSLog(@"damon");
[self jump:2];
}
return YES;
}