特定のスレッドへ通知を配信
通常の通知センターは、通知が送信されたスレッド上に通知を配信します。分散通知センターは、メインスレッド上に通知を配信します。時々、通知センターではなく、あなたが決定した特定のスレッドで通知が配信されることが必要な事があります。たとえば、バックグラウンド・スレッド上で実行されているオブジェクトが、ウィンドウが閉じるなど、ユーザーインターフェイスからの通知を待機している場合は、メインスレッドではなくバックグラウンド・スレッドで通知を受け取りたいでしょう。このような場合は、通知がデフォルトのスレッドで配信された時点で配信された時通知をキャプチャし、適切なスレッドにリダイレクトしなければなりません。
通知をリダイレクトする 1 つの方法は、カスタム通知キュー (NSNotificationQueue オブジェクトではない) を使用して、不正なスレッド上で受信された全ての通知を保持し、正しいスレッド上で処理することです。この手法は以下のように機能します。通常のように、通知を登録します。通知が到着すると、現在のスレッドが通知を処理すべきスレッドであるかどうかをテストします。スレッドが間違っている場合は、通知をキューに格納し、通知が処理されるべきことを示すシグナルを正しいスレッドに送信します。もう一方のスレッドはシグナルを受信し、キューから通知を削除し、通知を処理します。
この手法を実装するには、監視者オブジェクトが、以下の値を持つインスタンス変数を持つ必要があります:通知を保持する変更可能な配列、正しいスレッド (Machポート) を通知する通信ポート、通知配列とのマルチスレッド競合を防ぐためのロック、正しいスレッド (NSThread オブジェクト) を識別する値です。また、変数を設定し、通知を処理し、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
通知を登録する前に、プロパティを初期化する必要があります。以下のメソッドは、キューおよびロックオブジェクトを初期化し、現在のスレッドオブジェクトへの参照を保持し、Mach 通信ポートを作成して、現在のスレッドの実行ループに追加します。
- (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 メッセージが到着したときに受信スレッドの実行ループが実行されていない場合、カーネルは次回実行ループに入るまでメッセージを保持します。受信スレッドの実行ループは、着信メッセージをポートのデリゲートの handleMachMessage: メソッドに送信します。
この実装では、notificationPort に送信されるメッセージには情報は含まれません。代わりに、スレッド間で渡される情報は通知配列に含まれます。Mach メッセージが到着すると、handleMachMessage: メソッドはメッセージの内容を無視し、処理を必要とする全ての通知の notification 配列をチェックします。通知は配列から削除され、実際の通知処理メソッドに転送されます。同時に送信されるポートメッセージが多すぎると、ポートメッセージは破棄される可能性があるため、handleMachMessage: メソッドは配列が空になるまで配列を反復処理します。メソッドは通知配列にアクセスするときにロックを獲得して、通知を追加する 1 つのスレッドと配列から別の通知を削除する間の競合を防止しなければなりません。
- (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; } }
最後に、送信されるべきスレッドに関係なく現在のスレッドに通知を登録するには、setUpThreadingSupport を呼び出してオブジェクトの通知プロパティを初期化し、通知を通常どおり登録し、特別な通知処理メソッドをセレクタとして指定しなければなりません。
[self setupThreadingSupport]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"NotificationName" object:nil];
この実装はいくつかの側面で制限されています。まず、このオブジェクトによって処理されるすべてのスレッド通知は、同じメソッド (processNotification:) を通過しなければなりません。第 2 に、各オブジェクトは独自の実装と通信ポートを提供しなければなりません。より優れているがより複雑な実装では、 NSNotificationCenter のサブクラスまたは各スレッドごとに 1 つの通知キューを持つ別のクラスに動作を一般化し、複数の監視者オブジェクトやメソッドに通知を送信できます。