Использование обратных вызовов блока к основному потоку из подкласса NSOperation (ARC) - PullRequest
4 голосов
/ 20 октября 2011

Этот вопрос похож на этот вопрос с добавлением автоматического подсчета ссылок.

У меня есть подкласс NSOperation, который принимает аргумент блока, который предназначен в качестве обратного вызова для основного(UI) поток.Мое первоначальное намерение состояло в том, чтобы выполнить некоторую операцию в фоновом режиме, а затем использовать dispatch_async и основную очередь для выполнения обратного вызова.

Исходное условие:

@interface MySubclass : NSOperation {
@protected
    dispatch_block_t _callback;
}

- (id)initWithCallback:(dispatch_block_t)callback;

@end

@implementation MySubclass

- (void)main
{
    // Do stuff

    if (![self isCancelled]) { 
        dispatch_async(dispatch_get_main_queue(), _callback);
    }   
}

@end 

Проблемы возникают, когда все ссылкик объекту UIKit вне области блока удаляются.(Например, UIViewController выталкивается из стека навигации.) Это оставляет единственную ссылку на объект внутри блока, поэтому объект освобождается, когда блок находится, в потоке, где блок освобожден ,Отмена выделения объекта UIKit из основного потока приводит к сбою приложения с сообщением об ошибке Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...

В качестве обходного пути я добавил модификатор __block в callar ivar и использую dispatch_sync, чтобы убедиться, что всеРелиз находится в главном потоке.

@interface MySubclass : NSOperation {
@protected
    __block dispatch_block_t _callback;
}
- (id)initWithCallback:(dispatch_block_t)callback;

@end

@implementation MySubclass

- (void)main
{
    // Do Stuff

    if (![self isCancelled]) {
        dispatch_block_t block = ^{
            _callback();
            _callback = nil;
        };

        // Cover all our bases to prevent deadlock
        if ([NSThread isMainThread]) block();
        else dispatch_sync(dispatch_get_main_queue(), block);
    }
}

@end

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

Ответы [ 2 ]

4 голосов
/ 20 октября 2011

Если вам нужно убедиться, что обратный вызов выполняется, даже если контроллер извлечен из стека, тогда ваш обходной путь правильный.

Если, однако, вам действительно нужен только обратный вызов для запуска, если контроллер все еще работает, тогда было бы проще использовать слабые ссылки в обратном вызове, чтобы гарантировать, что сам блок не сохраняет контроллер в первую очередь , Это будет выглядеть примерно так:

- (void)demoMethod {
    __weak id weakSelf = self;
    MySubclass *subclass = [[MySubclass alloc] initWithCallback:^{
        if (!weakSelf) {
            return;
        }
        else {
            // Do whatever the callback does here
        }
    }];

    // Do something with `subclass` here
}
1 голос
/ 20 октября 2011

Пользователи вашего API должны поддерживать слабые ссылки на UIViews и любые другие объекты с этой проблемой.Обратный вызов больше не будет держать UIView вокруг.Внутри блока они должны присвоить слабую ссылку сильной ссылке, проверить эту сильную ссылку на нуле и действовать соответствующим образом.

Контроллеры представления должны быть осторожны, чтобы не создавать ненужные экземпляры своих представлений.Всегда используйте [self isViewLoaded] до доступа к [self view].(Это также относится к [self tableView] в UITableView подклассах, поскольку это просто правильно введенный псевдоним для view.)

...