Поставка уведомлений определенным потокам

Регулярные центры уведомления поставляют уведомления на потоке, в котором было отправлено уведомление. Распределенные центры уведомления поставляют уведомления на основном потоке. Время от времени можно потребовать, чтобы уведомления были поставлены на определенном потоке, определяющемся Вами вместо центра уведомления. Например, если объект, работающий в фоновом потоке, прислушивается к уведомлениям от пользовательского интерфейса, таким как закрытие окна, требуется получить уведомления, в фоновом режиме распараллеливают вместо основного потока. В этих случаях необходимо получить уведомления, когда они поставлены на потоке по умолчанию и перенаправляют их к надлежащему потоку.

Один способ перенаправить уведомления состоит в том, чтобы использовать пользовательскую очередь уведомления (не NSNotificationQueue объект) для содержания любых уведомлений, полученных на неправильных потоках и затем обрабатывающих их на корректном потоке. Этот метод работает следующим образом. Вы обычно регистрируетесь для уведомления. Когда уведомление поступает, Вы тестируете, является ли текущий поток потоком, который должен обработать уведомление. Если это - неправильный поток, Вы храните уведомление в очереди и затем отправляете сигнал в корректный поток, указывая, что для уведомления нужна обработка. Другой поток получает сигнал, удаляет уведомление из очереди и обрабатывает уведомление.

Для реализации этого метода объект наблюдателя должен иметь переменные экземпляра для следующих значений: непостоянный массив для содержания уведомлений, коммуникационного порта для сигнализации корректного потока (порт Маха), блокировка для предотвращения многопоточности конфликтует с массивом уведомления и значением, идентифицирующим корректный поток ( NSThread объект). Вам также нужны методы, чтобы установить переменные, обработать уведомления и получить сообщения Маха. Вот необходимые определения для добавления к классу объекта наблюдателя.

@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

Прежде, чем зарегистрироваться для любых уведомлений, необходимо инициализировать свойства. Следующий метод инициализирует объекты очереди и объекты блокирования, сохраняет ссылку на объект текущего потока и создает коммуникационный порт Маха, который это добавляет к циклу выполнения текущего потока.

- (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 получены в цикле выполнения потока, сначала выполнившего этот метод. Если цикл выполнения потока получения не работает, когда сообщение Маха поступает, ядро держится за сообщение до следующего раза, когда цикл выполнения вводится. Цикл выполнения потока получения отправляет входящие сообщения в handleMachMessage: метод делегата порта.

В этой реализации никакая информация не содержится в сообщениях, отправленных в notificationPort. Вместо этого информация, переданная между потоками, содержится в массиве уведомления. Когда сообщение Маха поступает, handleMachMessage: метод игнорирует содержание сообщения и просто проверяет notifications массив для любых уведомлений та обработка потребности. Уведомления удалены из массива и пересланы к реальному методу обработки уведомления. Поскольку сообщения порта могут быть отброшены, если слишком многие отправляются одновременно, 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];
}

Когда уведомление поставлено Вашему объекту, метод, получающий уведомление, должен идентифицировать, работает ли это в корректном потоке или нет. Если это - корректный поток, уведомление обычно обрабатывается. Если это - неправильный поток, уведомление добавляется к очереди и сообщенному порту уведомлений.

- (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:). Во-вторых, каждый объект должен обеспечить свою собственную реализацию и коммуникационный порт. Лучшая, но более сложная, реализация обобщила бы поведение в любого подкласс NSNotificationCenter или отдельный класс, который имел бы одну очередь уведомления для каждого потока и был бы в состоянии поставить уведомления многократным объектам наблюдателя и методам.