NSNotification&NSNotificationCenter(實現(xiàn)原理,多線程,內(nèi)存管理角度)

http://www.cocoachina.com/ios/20150316/11335.html (通知愈多線程的關(guān)系)
http://www.reibang.com/p/2d3c8e084205 runloop相關(guān)的
http://www.reibang.com/p/a307587ac62c 通知中心
http://blog.csdn.net/wzzvictory/article/details/8489516
http://www.cnblogs.com/heyonggang/p/3681689.html 概念解析
http://www.cnblogs.com/wangyang1213/p/5234228.html
簡單的展示通知 在按鈕按下時傳值速和,

- (IBAction)buttonClick:(id)sender{
   //添加字典,將label的值通過key值設(shè)置傳遞
   NSDictionary *dict=[[NSDictionary alloc]initWithObjectsAndKeys:self.textFieldOne.text,@"textOne",self.textFieldTwo.text,@"textTwo",nil];
   //創(chuàng)建通知
   NSNotification*notification=[NSNotificationnotificationWithName:@"tongzhi"object:niluserInfo:dict];
   //通過通知中心發(fā)送通知
  [[NSNotificationCenterdefaultCenter] postNotification:notification];
  [self.navigationControllerpopViewControllerAnimated:YES];
 
}

在發(fā)送通知后慢叨,在所要接收的控制器中注冊通知監(jiān)聽者懂更,將通知發(fā)送的信息接收

- (void)viewDidLoad{
  [superviewDidLoad];
   //注冊通知
  [[NSNotificationCenterdefaultCenter]addObserver:self selector:@selector(tongzhi:) name:@"tongzhi" object:nil];
 
}
- (void)tongzhi:(NSNotification*)text{
  NSLog(@"%@",text.userInfo[@"textOne"]);
     NSLog(@"-----接收到通知------");
 
}

移除通知:removeObserver:和removeObserver:name:object:
其中,removeObserver:是刪除通知中心保存的調(diào)度表一個觀察者的所有入口,而removeObserver:name:object:是刪除匹配了通知中心保存的調(diào)度表中觀察者的一個入口喻旷。
這個比較簡單,直接調(diào)用該方法就行牢屋。例如:

[[NSNotificationCenter defaultCenter] removeObserver:observername:nil object:self];

注意參數(shù)notificationObserver為要刪除的觀察者掰邢,一定不能置為nil。

從使用的角度上考慮 通知中心屬于十分簡單的
我們從三個角度來理解 通知中心, 原理,多線程角度,內(nèi)存管理角度等三方面深入理解他.


1.實現(xiàn)原理,

我們可以看到 通知中心里面是兩個類 NSNotificationCenter NSNotification 而 NSNotificationCenter 是用來統(tǒng)一管理整體的通知消息

使用時 還是把 NSNotificationCenter 以單利的形式創(chuàng)建 通過 center post 通知
通過center addObserver:(id)observer selector:(SEL)Selector name:(NSString *)Name object:(id)Object 建立監(jiān)聽
,通知中心的優(yōu)點在于 一處發(fā)送,全局接受.那么也就意味著 轉(zhuǎn)發(fā)和監(jiān)聽同步的
addObserver:(id)observer selector:(SEL)Selector name:(NSString *)Name object:(id)Object 里面做了
將 name Selector Object 賦值給一個model 并且每注冊一個監(jiān)聽,就會給一個model 賦值 并且 model添加到一個可變數(shù)組中

當我們post 通知是 我們就去便利這個可變數(shù)組,并且取得數(shù)組中的model 這時候我們就可以拿到 通知的name,去執(zhí)行通知的對象 ,以及執(zhí)行的通知的方法,
[對象 performSelector:selector withObject:notification];
那么就好啦 這時候相應(yīng)的對象就去只想這個selector啦并且將參數(shù) notification傳遞過去啦
還有就是 remove 就是從數(shù)組中移除就好
一個通正中心簡單的原理就是這樣,當然還有更加會有多線程一會解釋


2.多線程的角度考慮
讀官方文檔時多次提到 通知是在那個線程發(fā)送的就在那個線程接受.
也就是會涉及到一個問題就是我實在次級線程post 然后想在其他線程接受那該怎么辦
官網(wǎng)文檔給的方法就是重定向
代碼如下:

@interface ViewController () @property (nonatomic) NSMutableArray    *notifications;         // 通知隊列
@property (nonatomic) NSThread          *notificationThread;    // 期望線程
@property (nonatomic) NSLock            *notificationLock;      // 用于對通知隊列加鎖的鎖對象伟阔,避免線程沖突
@property (nonatomic) NSMachPort        *notificationPort;      // 用于向期望線程發(fā)送信號的通信端口
 
@end
 
@implementation ViewController
 
- (void)viewDidLoad {
    [super viewDidLoad];
 
    NSLog(@"current thread = %@", [NSThread currentThread]);
 
    // 初始化
    self.notifications = [[NSMutableArray alloc] init];
    self.notificationLock = [[NSLock alloc] init];
 
    self.notificationThread = [NSThread currentThread];
    self.notificationPort = [[NSMachPort alloc] init];
    self.notificationPort.delegate = self;
 
    // 往當前線程的run loop添加端口源
    // 當Mach消息到達而接收線程的run loop沒有運行時,則內(nèi)核會保存這條消息掰伸,直到下一次進入run loop
    [[NSRunLoop currentRunLoop] addPort:self.notificationPort
                                forMode:(__bridge NSString *)kCFRunLoopCommonModes];
 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"TestNotification" object:nil];
 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 
        [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil userInfo:nil];
 
    });
}
 
- (void)handleMachMessage:(void *)msg {
 
    [self.notificationLock lock];
 
    while ([self.notifications count]) {
        NSNotification *notification = [self.notifications objectAtIndex:0];
        [self.notifications removeObjectAtIndex:0];
        [self.notificationLock unlock];
        [self processNotification:notification];
        [self.notificationLock lock];
    };
 
    [self.notificationLock unlock];
}
 
- (void)processNotification:(NSNotification *)notification {
 
    if ([NSThread currentThread] != _notificationThread) {
        // Forward the notification to the correct thread.
        [self.notificationLock lock];
        [self.notifications addObject:notification];
        [self.notificationLock unlock];
        [self.notificationPort sendBeforeDate:[NSDate date]
                                   components:nil
                                         from:nil
                                     reserved:0];
    }
    else {
        // Process the notification here;
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"process notification");
    }
}
 
@end

上面的方法是將子線程通知到主線程中去執(zhí)行任務(wù)
當然如果想在某個子線稱重執(zhí)行那么稍微修改一下就搞定啦

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        self.notificationThread = [NSThread currentThread];
 NSLog(@"指定current thread = %@", self.notificationThread);
        self.notificationPort = [[NSMachPort alloc] init];
        self.notificationPort.delegate = self;
        
        [[NSRunLoop currentRunLoop] addPort:self.notificationPort
                                    forMode:(__bridge NSString *)kCFRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    });
創(chuàng)建子線程并且為其開啟runloop,這樣就搞定啦可以再指定的子線程中去執(zhí)行通知

通告隊列

NSNotificationQueue
對象(或者簡單稱為通告隊列)的作用是充當通告中心(NSNotificationCenter
的實例)的緩沖區(qū)皱炉。通告隊列通常以先進先出(FIFO)的順序維護通告。當一個通告上升到隊列的前面時狮鸭,隊列就將它發(fā)送給通告中心合搅,通告中心隨后將它派發(fā)給所有注冊為觀察者的對象。

每個線程都有一個缺省的通告隊列歧蕉,與任務(wù)的缺省通告中心相關(guān)聯(lián)灾部。圖5-9展示了這種關(guān)聯(lián)。您可以創(chuàng)建自己的通告隊列惯退,使得每個線程和通告中心有多個隊列赌髓。

**圖5-9 **通告隊列和通告中心

聚結(jié)的通告

NSNotificationQueue
類為Foundation Kit的通告機制增加了兩個重要的特性:即通告的聚結(jié)和異步發(fā)送。聚結(jié)是把和剛進入隊列的通告相類似的其它通告從隊列中移除的過程催跪。如果一個新的通告和已經(jīng)在隊列中的通告相類似锁蠕,則新的通告不進入隊列,而所有類似的通告(除了隊列中的第一個通告以外)都被移除懊蒸。然而荣倾,您不應(yīng)該依賴于這個特殊的聚結(jié)行為。

您可以為enqueueNotification:postingStyle:coalesceMask:forModes:
方法的第三個參數(shù)指定如下的一或多個常量骑丸,指示簡化的條件:

NSNotificationNoCoalescing

NSNotificationCoalescingOnName

NSNotificationCoalescingOnSender

您可以對NSNotificationCoalescingOnName
和NSNotificationCoalescingOnSender
常量進行位或操作舌仍,指示Cocoa同時使用通告名稱和通告對象進行聚結(jié)。那樣的話通危,和剛剛進入隊列的通告具有相同名稱和發(fā)送者對象的所有通告都會被聚結(jié)铸豁。
異步發(fā)送通告

通過NSNotificationCenter
類的postNotification:
方法及其變體,您可以將通告立即發(fā)送給通告中心黄鳍。但是推姻,這個方法的調(diào)用是同步的:即在通告發(fā)送對象可以繼續(xù)執(zhí)行其所在線程的工作之前,必須等待通告中心將通告派發(fā)給所有的觀察者并將控制權(quán)返回框沟。但是藏古,您也可以通過NSNotificationQueue
的enqueueNotification:postingStyle:
和enqueueNotification:postingStyle:coalesceMask:forModes:
方法將通告放入隊列增炭,實現(xiàn)異步發(fā)送,在把通告放入隊列之后拧晕,這些方法會立即將控制權(quán)返回給調(diào)用對象隙姿。

Cocoa根據(jù)排隊方法中指定的發(fā)送風格和運行循環(huán)模式來清空通告隊列和發(fā)送通告。模式參數(shù)指定在什么運行循環(huán)模式下清空隊列厂捞。舉例來說输玷,如果您指定NSModalPanelRunLoopMode
模式,則通告只有當運行循環(huán)處于該模式下才會被發(fā)送靡馁。如果當前運行循環(huán)不在該模式下欲鹏,通告就需要等待,直到下次運行循環(huán)進入該模式臭墨。

向通告隊列發(fā)送通告可以有三種風格:NSPostASAP
赔嚎、NSPostWhenIdle
、和NSPostNow
胧弛,這些風格將在接下來的部分中進行說明尤误。

盡快發(fā)送

以NSPostASAP
風格進入隊列的通告會在運行循環(huán)的當前迭代完成時被發(fā)送給通告中心,如果當前運行循環(huán)模式和請求的模式相匹配的話(如果請求的模式和當前模式不同结缚,則通告在進入請求的模式時被發(fā)出)损晤。由于運行循環(huán)在每個迭代過程中可能進行多個調(diào)用分支(callout),所以在當前調(diào)用分支退出及控制權(quán)返回運行循環(huán)時红竭,通告可能被分發(fā)尤勋,也可能不被分發(fā)。其它的調(diào)用分支可能先發(fā)生茵宪,比如定時器或由其它源觸發(fā)了事件斥黑,或者其它異步的通告被分發(fā)了。

您通趁汲可以將NSPostASAP
風格用于開銷昂貴的資源锌奴,比如顯示服務(wù)器。如果在運行循環(huán)的一個調(diào)用分支過程中有很多客戶代碼在窗口緩沖區(qū)中進行描畫憾股,在每次描畫之后將緩沖區(qū)的內(nèi)容刷新到顯示服務(wù)器的開銷是很昂貴的鹿蜀。在這種情況下,每個draw...
方法都會將諸如“FlushTheServer” 這樣的通告排入隊列服球,并指定按名稱和對象進行聚結(jié)茴恰,以及使用NSPostASAP
風格。結(jié)果斩熊,在運行循環(huán)的最后往枣,那些通告中只有一個被派發(fā),而窗口緩沖區(qū)也只被刷新一次。

空閑時發(fā)送

以NSPostWhenIdle
風格進入隊列的通告只在運行循環(huán)處于等待狀態(tài)時才被發(fā)出分冈。在這種狀態(tài)下圾另,運行循環(huán)的輸入通道中沒有任何事件,包括定時器和異步事件雕沉。以NSPostWhenIdle
風格進入隊列的一個典型的例子是當用戶鍵入文本集乔、而程序的其它地方需要顯示文本字節(jié)長度的時候。在用戶輸入每一個字符后都對文本輸入框的尺寸進行更新的開銷是很大的(而且不是特別有用)坡椒,特別是當用戶快速輸入的時候扰路。在這種情況下,Cocoa會在每個字符鍵入之后倔叼,將諸如“ChangeTheDisplayedSize”這樣的通告進行排隊汗唱,同時把聚結(jié)開關(guān)打開,并使用NSPostWhenIdle
風格丈攒。當用戶停止輸入的時候渡嚣,隊列中只有一個“ChangeTheDisplayedSize”通告(由于聚結(jié)的原因)會在運行循環(huán)進入等待狀態(tài)時被發(fā)出,顯示部分也因此被刷新肥印。請注意,運行循環(huán)即將退出(當所有的輸入通道都過時的時候绝葡,會發(fā)生這種情況)時并不處于等待狀態(tài)深碱,因此也不會發(fā)出通告。

立即發(fā)送
以NSPostNow
風格進入隊列的通告會在聚結(jié)之后藏畅,立即發(fā)送到通告中心敷硅。您可以在不需要異步調(diào)用行為的時候 使用NSPostNow
風格(或者通過NSNotificationCenter的postNotification:
方法來發(fā)送)。在很多編程環(huán)境下愉阎,我們不僅允許同步的行為绞蹦,而且希望使用這種行為:即您希望通告中心在通告派發(fā)之后返回,以便確定觀察者對象收到通告并進行了處理榜旦。當然幽七,當您希望通過聚結(jié)移除隊列中類似的通告時,應(yīng)該用enqueueNotification
...方法溅呢,且使用NSPostNow
風格澡屡,而不是使用postNotification:
方法。

發(fā)送通知時,這么寫 創(chuàng)建notification 然后加入隊列 并且選擇發(fā)送模式
 NSNotification *notifacation = [[NSNotification alloc]initWithName:@"qqqq" object:nil userInfo:nil];
    [[NSNotificationQueue defaultQueue] enqueueNotification:notifacation postingStyle:3];

合并通知
一些情況下咐旧,某一事件一旦發(fā)生驶鹉,開發(fā)者可能只想要至少發(fā)送一次通知,即使該事件可能頻繁多次發(fā)生铣墨。此時即可采用合并通知室埋,合并是把和剛進入隊列的通知相類似的其它通知從隊列中移除的過程。如果一個新的通知和已經(jīng)在隊列中的通知相類似,則新的通知不進入隊列姚淆,而所有類似的通知(除了隊列中的第一個通知以外)都被移除孕蝉。**但是,為安全起見肉盹,開發(fā)者不應(yīng)該完全依賴于這個特殊的合并行為昔驱。
我想說說 合并并沒有用過

3.從內(nèi)存管理的角度考慮
使用通知中心時,有事會報錯:thread1:exc_bad)access(code=exc_1386_gpflt)
這個錯誤的意思就是你想一個已經(jīng)釋放的對象發(fā)送了消息
那么我們回顧一下 通話中中心的使用啊
當我們 注冊一個通知中心 并且對其進行監(jiān)聽時 我們會 把自身的類 也就是self 還有通知的名字 已經(jīng)處理通知結(jié)果的的方法,放入一個model中并將model添加到一個可變數(shù)組中.當我們?nèi)ost通知時,從數(shù)組中遍歷 model 然后取出model中的值 進行
[對象 performSelector:selector withObject:notification];
然而 如果監(jiān)聽通知的類這是已經(jīng)被釋放 那么 這時再去post 那么就會想一個已經(jīng)釋放了的對象進行發(fā)送消息那么 自然而然的就會報錯.那么解決這種錯誤的方法就是
移除通知.
(1)那么為什么移除通知就不會再崩潰了呢
首先我們要理解remove的原理,前面我們知道 監(jiān)聽時會給model賦值 然后將model添加可變數(shù)組中,然后post時 便利數(shù)組取出model.根據(jù)model 去[對象 performSelector:selector withObject:notification];發(fā)送消息.
那么如果我們在可變數(shù)組中直接移除了某個已經(jīng)釋放對象的model,那么post時 就不會遍歷到相應(yīng)的model,也不會[對象 performSelector:selector withObject:notification];發(fā)送消息. 那么自然就不會發(fā)送消息崩潰
(2)如果 我們在vc1 中post 通知 在vc2中監(jiān)聽通知
我們做這樣一個通知 從vc1 跳轉(zhuǎn)到vc2 這是vc2 進行了注冊和監(jiān)聽,這是我們pop返回vc1 這是vc2 已經(jīng)釋放,這時 我們在vc1中點擊post 發(fā)送通知,你會發(fā)現(xiàn)不科學,因為沒有拋出異常.難道是理論出了錯誤. 這是我們隊 center創(chuàng)建一個分類,然后重新remove方法,(這里其實已經(jīng)覆蓋了原方法),那么在remove方法中進行打印,再重復(fù)剛才的步驟,發(fā)現(xiàn)分類執(zhí)行了remove并且打印了 同時也拋出了異常
這說明,系統(tǒng)自動幫我們執(zhí)行了 remove

好了 就到這了吧 各位道友有啥建議或者想法 歡迎評論

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市上忍,隨后出現(xiàn)的幾起案子骤肛,更是在濱河造成了極大的恐慌,老刑警劉巖窍蓝,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腋颠,死亡現(xiàn)場離奇詭異,居然都是意外死亡吓笙,警方通過查閱死者的電腦和手機淑玫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來面睛,“玉大人絮蒿,你說我怎么就攤上這事∪” “怎么了土涝?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長幌墓。 經(jīng)常有香客問我但壮,道長,這世上最難降的妖魔是什么常侣? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任蜡饵,我火速辦了婚禮,結(jié)果婚禮上胳施,老公的妹妹穿的比我還像新娘溯祸。我一直安慰自己,他們只是感情好舞肆,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布您没。 她就那樣靜靜地躺著,像睡著了一般胆绊。 火紅的嫁衣襯著肌膚如雪氨鹏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天压状,我揣著相機與錄音仆抵,去河邊找鬼跟继。 笑死,一個胖子當著我的面吹牛镣丑,可吹牛的內(nèi)容都是我干的舔糖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼莺匠,長吁一口氣:“原來是場噩夢啊……” “哼金吗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起趣竣,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤摇庙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后遥缕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卫袒,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年单匣,在試婚紗的時候發(fā)現(xiàn)自己被綠了夕凝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡户秤,死狀恐怖码秉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鸡号,我是刑警寧澤转砖,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站膜蠢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏莉兰。R本人自食惡果不足惜挑围,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望糖荒。 院中可真熱鬧杉辙,春花似錦、人聲如沸捶朵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽综看。三九已至品腹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間红碑,已是汗流浹背舞吭。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工泡垃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人羡鸥。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓蔑穴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親惧浴。 傳聞我的和親對象是個殘疾皇子存和,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,265評論 25 707
  • 25. 什么時候用delegate,什么時候用Notification衷旅? 1捐腿、Delegate是一種點對點的消息傳...
    煙雨平生花飛舞閱讀 436評論 0 0
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,160評論 30 470
  • 37.cocoa內(nèi)存管理規(guī)則 1)當你使用new,alloc或copy方法創(chuàng)建一個對象時芜茵,該對象的保留計數(shù)器值為1...
    如風家的秘密閱讀 854評論 0 4
  • 剛剛讀完《想再次成為幸福的新娘》九串,講真绞佩,我是為了朋友才讀這本書。她在三年前結(jié)束了一段短暫的婚姻猪钮,一直單身至今品山。 我...
    Shield_MA閱讀 239評論 0 0