談談 NSNotification

    notification這東西比較nb的地方在於程序間低耦合,達到了通過name就可以調用八杆子都達不到的方法。但是帶來的壞處就是程序不容易維護,有人會說低耦合不就是帶來程序的擴展的易於維護嗎?我說的不易維護主要集中在name上面,比如程序中大量的使用notificatioin,那麼name就很容易衝突,並且如果衝突了是很難發現了,IDE不會畫出一到紅線告訴你的,當然如果程序規範好了是很容易解決這個問題的。notification的性能沒有普通的方法調用(消息發送)高。所以最好是有“廣播”需求的時候再用吧,以及特殊需求時再用吧。
    NSNotification包含3個信息:name,發送者對象,userinfo。它只能在相同的線程中傳遞。如果想要在不同線程中傳遞還需要藉助信號量。在相同線程中傳遞通知是同步的,但是可以藉助NSNotificationQueue達到異步的效果。NSNotificationQueue也必須藉助run loop達到在同一個線程中的異步效果。

消息同步發送:

// 註冊監聽[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveRemoteRequest:) name:kSendImage object:nil];
// 發送notify
[[NSNotificationCenter defaultCenter] postNotificationName:url.path object:nil userInfo:parameters];



初次使用,會感覺這個東西和神祕。一定使用了某種獨立服務進程。但是通過查看它的線程棧發現,它的實現只是通過普通代碼調用得到。

上圖中,下面的紅框是notify發送代碼所在位置,上面的紅框是接收端使用的代碼。說明notify是直接通過方法調用實現的。並且沒有進行多線程處理。消息異步發送:

// 註冊監聽[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveRemoteRequest:) name:kSendImage object:nil];

// 異步發佈消息

NSNotification *notification = [[NSNotification alloc] initWithName:url.path object:nil userInfo:parameters];

NSNotificationQueue *notificationQueue = [NSNotificationQueue defaultQueue];

[notificationQueue enqueueNotification:notification postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnSender forModes:nil];



這裏enqueueNotification:postingStyle:coalesceMask:forModes方法的最後一個參數指定了runloop的modle。說明消息以什麼modle放入runloop中。coalesceMask參數指名了notify放入runloop時,如果runloop中已經有相同的name的notify了,就忽略這次的notify,也就是合併。

for(int i=0;i<5;i++){
        // 異步發佈消息
        NSNotification *notification = [[NSNotificationalloc] initWithName:url.pathobject:niluserInfo:parameters];
        NSNotificationQueue *notificationQueue = [NSNotificationQueuedefaultQueue];
        [notificationQueue enqueueNotification:notification postingStyle:NSPostASAPcoalesceMask:NSNotificationCoalescingOnNameforModes:nil];
    }

我們連續發送了5次通知,發現,只接收到了一次。

for(int i=0;i<5;i++){
        NSDate *date = [NSDate date];
        // 這是一個阻塞操作,它和sleep不同的是,sleep單純的阻塞到指定時間,這個線程內的所以方法都不能運行
        // 而它是阻塞了當前的task,然後執行其他task,等到其他task執行完或者時間到後這個方法返回
        [[NSRunLoop currentRunLoop] runUntilDate:[date dateByAddingTimeInterval:1]];
        //            sleep(1);

        // 異步發佈消息
        NSNotification *notification = [[NSNotification alloc] initWithName:url.path object:nil userInfo:parameters];
        NSNotificationQueue *notificationQueue = [NSNotificationQueue defaultQueue];
        [notificationQueue enqueueNotification:notification postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnSender forModes:nil];
    }

通過以上代碼,我們在一個notification放入隊列準備在runloop中發送完後,添加下一個notifivation對象。這裏不用sleep的原因是,sleep會阻塞整個線程。主線程中的所有task都不能運行了。由於圖形界面也中主線程中處理,所以你會發現阻塞的過程中,屏幕是黑的。使用[NSRunLoop runUntilDate]方法在運行中是阻塞的,而且它是用來運行run loop 中的task的。所以它即保證了阻塞任務遲點放入run loop,又能讓run loop中的任務正常運行。

對應notify不能在線程間傳遞,官方給出瞭解決辦法:

/* Threaded notification support. */

// 保持notification對象
@property NSMutableArray *notifications;

// 目標線程
@property NSThread *notificationThread;

// 保證notifications的線程安全
@property NSLock *notificationLock;

// 向正確的線程發送信號
@property NSMachPort *notificationPort;

 

- (void) setUpThreadingSupport;

- (void) handleMachMessage:(void *)msg;

- (void) processNotification:(NSNotification *)notification;

@end

- (void) setUpThreadingSupport {
    if (self.notifications) {
        return;
    }
    self.notifications      = [[NSMutableArray alloc] init];
    self.notificationLock   = [[NSLock alloc] init];
    self.notificationThread = [NSThread currentThread];
 
    self.notificationPort = [[NSMachPort alloc] init];
    [self.notificationPort setDelegate:self];
    [[NSRunLoop currentRunLoop] addPort:self.notificationPort
            forMode:(NSString __bridge *)kCFRunLoopCommonModes];
}

當執行上面的方法後,任何被髮送到notificationPort的消息,都被當前runloop接收,如果當前的線程的run loop沒有運行,內核會保存這個消息直到run loop運行。然後run loop會將消息傳遞到代理方法handleMachMessage方法中。

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

上面的代碼,是當接收到馬赫信號時,遍歷notification list,並將通知傳遞給處理方法。

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

通知處理方法,首先判斷這個通知是否是本線程的,如果是就處理通知如果不是,就把通知對象放入隊列,併發送馬赫信號。

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

通知的監聽,只需要這前面加上安裝程序即可。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章