[iOS] Notification Programming Topics --- Delivering Notifications To Particular Threads

此份文件為個人學習通知中心的翻譯,內文請參考蘋果官方文件,若有翻譯錯誤請見諒,請勿挪作商業用途,並著明出處。


常規的通知中心會在貼出通知的線程上傳遞通知。分佈式的通知中心(Distributed
notification centers)會在主線程(main thread)上傳遞通知。有時你會要求在你所決定的特定線程上傳遞通知而不是由通知中心決定。舉例來說,在背景線程運作的物件在注意從UI(user interface)來的通知,像是視窗關閉,你會希望在背景線程接收通知而不是主線程。在這些情況下,你必須在通知於欲定線程中傳遞時捕捉它們並重新將它們導向適當的線程。

一種重新導向通知的方法是,使用客制的通知列隊(custom notification queue)(不是NSNotificationQueue物件)去擁有任何在不正確線程被接收的通知,然後把它們導向正確的線程。做法如下。正常的註冊接收通知。當通知抵達時,你測試目前的線程是否應該處理這個通知。若不是,你把通知存到隊列(queue)中,然後送出一個訊號(signal)到正確的線程去,指示需要處理(processing)的通知。其他的線程會收到訊號(signal),從隊列中移除通知並且處理(process)通知。這個技巧的實作,你的觀察物件需要有如下列數值的實例變數:持有通知的可變陣列(array)、發送信號到正確線程的(Mach port)交流阜(communication port)、避免多線程與通知陣列衝突的鎖(lock)以及一個用來識別正確線程的值(一個NSThread物件)。你也需要方法去設定(setup)這些值、處理通知和接收Mach訊息。這裡有用來加到你的觀察者物件的類別的必要定義。

@interface MyThreadedClass: NSObject
/* Threaded notification support. */
@property NSMutableArray *notifications;
@property NSThread *notificationThread;
@property NSLock *notificationLock;
@property NSMachPort *notificationPort;
- (void) setUpThreadingSupport;
- (void) handleMachMessage:(void *)msg;
- (void) processNotification:(NSNotification *)notification;
@end

在註冊任何通知前,你需要將這些特性(properties)初始化。以下的方法會初始化隊列和鎖頭物件(lock object),保持一個到目前線程物件的參照以及創建一個Mach交流阜(Mach communication port),這會加到目前線程運作迴圈。

- (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的訊息都會在線程第一次運作這個方法時的迴圈中被接收。若接收的線程運作迴圈在Mach訊息到達時沒在運作,kernel會將訊息暫停直到下一次運作迴圈進入時。貼收現程運作迴圈會送出進入的訊息到阜委任(port’s delegate)的handleMachMessage:方法去。

在這實作中,送到notificationPort的訊息不會包含任何資訊(information)。取而代之的,在線程之間傳送的資訊(information)會包含在通知陣列內。當Mach訊息抵達時,handleMachMessage:方法會忽略訊息內容,並且只對任何需要處理的通知的陣列檢查陣列。通知會從陣列中移除並且傳送(forward)到真實的通知處理方法。因為如果有太多阜訊息同時傳送,阜訊息可能會被刪除(dropped),handleMachMessage:方法會對陣列遞迴直到陣列空了。當進入(accessing)通知陣列時,這方法必須得到(acquire)一個鎖頭(lock)以避免一個加入通知的線程和另一個從陣列中移除通知的線程衝突。

- (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 port signaled)。

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

最後,註冊接收你要在目前線程傳遞的方法,不論它是在哪個線程中張貼,你都必須調用setUpThreadingSupport來初始化你物件的通知特性,然後正常的註冊通知,指明特定的通知處理方法做為selector。

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



這個實作在很多方面都受限制。首先,所有由這個物件處理之線程的通知(原文:all threaded notifications processed by this object)都必須透過同樣的方法傳遞 。第二,每一個物件都必須提供他自己的實作和交流阜。一個更好但更複雜的實作會一般化該行為成NSNotificationCenter的子類別或是一個不同的(separate)類別,對每一個線程都有隊列,並且能夠傳遞通知道多重的觀察者物件及方法。

留言