你可能不知道的Notification

Notification鸳玩,項(xiàng)目中使用還是蠻多的,post發(fā)送通知演闭,addObserver監(jiān)聽接收通知不跟,聽起來很簡單,對吧米碰。但是還是會有些大家可能會忽視的地方窝革。

同步or異步

[NotificationCenter defaultCenter] postNotification]购城,這種方式是同步的,并且在哪個線程發(fā)虐译,就在哪個線程收瘪板。

同步的意思,就是消息接收者全部處理完消息之后漆诽,post這方才會繼續(xù)往下執(zhí)行侮攀,因此,盡量不要做太耗時的操作拴泌。

由于消息收和發(fā)都在同一個線程中魏身。所以,盡量在主線程中post蚪腐,不然會引起不必要的麻煩箭昵,ui刷新問題,崩潰問題等等回季。

addObserver調(diào)用多次

addObserver如果添加多次家制,當(dāng)post的時候,也會收到多次泡一。類似這種:

 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:nil];
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:nil];

observer的移除

這個是老生常談了颤殴,一定記得要移除,否則崩潰很容易發(fā)生鼻忠。不過在iOS 9以后涵但,不需要手動移除。

If your app targets iOS 9.0 and later or OS X v10.11 and later, you don't need to unregister an observer in its deallocation method帖蔓。

異步通知

異步的好處矮瘟,不必等待所有的消息處理者處理完成,可以立馬返回塑娇。

如果要發(fā)送異步通知澈侠,可以使用NSNotificationQueue,將通知enqueueNotification之后埋酬,會在合適的時候?qū)⑼ㄖl(fā)送給NotificationCenter哨啃,NotificationCenter會真正的將消息post出去。

[[NSNotificationQueue defaultQueue] enqueueNotification:[NSNotification notificationWithName:@"task" object:self] postingStyle:NSPostWhenIdle];

可以指定runloop mode写妥,當(dāng)runloop處理該種mode的時候拳球,才會發(fā)送通知。

也可以指定發(fā)送通知的時機(jī)珍特,有如下3種醇坝。

typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1,
    NSPostASAP = 2,
    NSPostNow = 3
};

NSPostWhenIdle,在runloop空閑時發(fā)送次坡,當(dāng)runloop要退出時呼猪,不會發(fā)送。

NSPostASAP:Posting As Soon As Possible砸琅,在runloop的當(dāng)前迭代完成時發(fā)送給通知中心宋距,但是當(dāng)前mode和設(shè)定的mode要一致衰腌。

NSPostNow:就是同步調(diào)用莉掂。

聚合發(fā)送

當(dāng)在一段時間內(nèi),enqueue了多個通知岛杀,系統(tǒng)不會每個都發(fā)送诱篷,如果在隊(duì)列中已有該種通知壶唤,則不會進(jìn)入隊(duì)列,只保留第一個通知棕所。有如下3中可選方式闸盔。

typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
    NSNotificationNoCoalescing = 0,         // 不聚合
    NSNotificationCoalescingOnName = 1,         // 根據(jù)通知名聚合
    NSNotificationCoalescingOnSender = 2       // 根據(jù)發(fā)送者聚合
};

NSNotificationCoalescingOnName:根據(jù)通知名稱來聚合,如果在一段時間內(nèi)琳省,
NotificationName="UpdateMyProfileNotification"有多個迎吵,則將他們聚合起來,只發(fā)送一個针贬。

NSNotificationCoalescingOnSender:根據(jù)發(fā)送方來聚合击费。

我做了下測試,的確只收到一次通知桦他。并且是第一個通知蔫巩。

for (int i = 0; i < 2; i++) {
        NSNotification *notification = [NSNotification notificationWithName:@"TestNotification" object:@(i)];
        [[NSNotificationQueue defaultQueue] enqueueNotification:notification                     
        postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil];
    }

在指定線程接收通知

上面說到,收發(fā)都在一個線程中快压,如果想要做到圆仔,在某個指定的線程接收通知,該如何做呢嗓节?蘋果文檔上提及了實(shí)現(xiàn)思路荧缘。

先簡單介紹下mach port,它主要用來線程間通信拦宣。簡單來說截粗,就是接收線程中注冊NSMachPort,在另外的線程中使用此port發(fā)送消息鸵隧,則注冊線程會收到相應(yīng)消息绸罗,調(diào)用handleMachMessage來處理。

主要思路:

  1. 定義一個中間對象NotificationHandler豆瘫,用來專門接收通知珊蟀,包括一個隊(duì)列,接收通知的線程,mach port育灸,lock腻窒。

  2. 首先NotificationHandler會注冊一個通知,對應(yīng)的處理函數(shù)為processNotification磅崭,當(dāng)在其他線程中post時儿子,processNotification會被調(diào)用砸喻,進(jìn)行如下處理柔逼。如果收到的通知跟指定的線程一樣,則處理消息割岛,反之愉适,則添加到隊(duì)列,同時通過port發(fā)送消息給指定線程癣漆。注意多線程中维咸,對隊(duì)列的處理,要加鎖扑媚。

  3. 指定線程收到回調(diào)handleMachMessage腰湾,首先會將通知刪除,然后調(diào)用processNotification進(jìn)行處理疆股,繼續(xù)以上過程费坊。


- (instancetype)init {
    if (self = [super init]) {
        [self setUpThreadingSupport];

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"notification" object:nil];
    }

    return self;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)setUpThreadingSupport
{
    if (self.notifications) {
        return;
    }
    self.notifications = [NSMutableArray new];
    self.lock = [NSLock new];
    self.thread = [NSThread currentThread];

    self.port = [NSMachPort new];
    [self.port setDelegate:self];
    
    [[NSRunLoop currentRunLoop] addPort:self.port forMode:(__bridge NSString *) kCFRunLoopCommonModes];
}

- (void)handleMachMessage:(void *)msg
{
    [self.lock lock];

    // 由于大量port message在同時被發(fā)送時,可能會被丟棄旬痹,為了防止沒有處理到附井,這里遍歷數(shù)組來進(jìn)行處理。
    while ([self.notifications count]) {
        NSNotification *notification = [self.notifications objectAtIndex:0];
        [self.notifications removeObjectAtIndex:0];
        [self.lock unlock];
        [self processNotification:notification];
        [self.lock lock];
    }

    [self.lock unlock];
}

- (void)processNotification:(NSNotification *)notification
{
    NSThread *ct = [NSThread currentThread];
    // 不是指定線程
    if (ct != _thread) {
        [self.lock lock];
        // 添加到隊(duì)列
        [self.notifications addObject:notification];
        [self.lock unlock];

        // 通過mach port發(fā)送消息
        [self.port sendBeforeDate:[NSDate date] components:nil from:nil reserved:0];
    }else{
        NSLog(@"process notification %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
    }
}

如果两残,我們想要在主線程中接收通知永毅,在viewDidLoad中。

- (void)viewDidLoad {
      self.notificationHandler = [NotificationHandler new];

// 在子線程中post人弓,會在主線程中收到
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"post notification %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];

        [[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
    });
}

或者沼死,需要在某個子線程中接收,可以這樣崔赌。

- (void)viewDidLoad {
    [super viewDidLoad];

    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(startThread) object:nil];
    [self.thread start];
}

- (void)startThread {

    NSLog(@"startThread %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);

    self.notificationHandler = [NotificationHandler new];
    
    // 在另外的子線程中發(fā)送通知dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"post notification %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];

        [[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
    });

    // 線程中需要自己啟動runloop
    [[NSRunLoop currentRunLoop] run];
}

參考:
Notifications

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末意蛀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子健芭,更是在濱河造成了極大的恐慌县钥,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慈迈,死亡現(xiàn)場離奇詭異若贮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門谴麦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蠢沿,“玉大人,你說我怎么就攤上這事细移〔瑁” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵弧轧,是天一觀的道長。 經(jīng)常有香客問我碗殷,道長精绎,這世上最難降的妖魔是什么锌妻? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任搁吓,我火速辦了婚禮吭历,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晌区。我一直安慰自己,他們只是感情好恼五,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布灾馒。 她就那樣靜靜地躺著遣总,像睡著了一般。 火紅的嫁衣襯著肌膚如雪傅物。 梳的紋絲不亂的頭發(fā)上琉预,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機(jī)與錄音啄栓,去河邊找鬼也祠。 笑死,一個胖子當(dāng)著我的面吹牛堪旧,可吹牛的內(nèi)容都是我干的奖亚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼爆袍,長吁一口氣:“原來是場噩夢啊……” “哼陨囊!你這毒婦竟也來了蜘醋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤堂湖,失蹤者是張志新(化名)和其女友劉穎无蜂,沒想到半個月后蒙谓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體斥季,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酣倾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了置侍。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拦焚。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡赎败,死狀恐怖蠢甲,靈堂內(nèi)的尸體忽然破棺而出鹦牛,到底是詐尸還是另有隱情,我是刑警寧澤能岩,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布拉鹃,位于F島的核電站鲫忍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏悟民。R本人自食惡果不足惜射亏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望及舍。 院中可真熱鬧锯玛,春花似錦兼蜈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至知态,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間负敏,已是汗流浹背其做。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工妖泄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渊季。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓却汉,卻偏偏與公主長得像合砂,于是被迫代替她去往敵國和親源织。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

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

  • 深入理解RunLoop 由ibireme| 2015-05-18 |iOS,技術(shù) RunLoop 是 iOS 和 ...
    橙娃閱讀 854評論 1 2
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)囊颅,斷路器,智...
    卡卡羅2017閱讀 134,656評論 18 139
  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴嗅骄。溺森。 不如我直接引用一個最簡單的問題慕爬,以這個作為切入點(diǎn)好了 在ma...
    Mr_Baymax閱讀 2,757評論 1 17
  • 每一個想讓生活更有質(zhì)量的人都關(guān)注了書畫藝貳叁 畫畫對她而言姥卢, 不只是一種樂趣, 更是一種修行独榴。 早稻-野獸 201...
    書畫藝貳叁閱讀 511評論 0 1
  • 歲歲思君眼欲穿,年年醉等夢不還棺榔。 獨(dú)憐長夜不安睡,燈火闌珊只似閑症歇。 恨離去,幾千般忘晤,月星高照相知難题画。 今生敢問何時...
    離離陌上草閱讀 204評論 0 3