Изучение NSBlockOperation

Я большой поклонник блоков, но не использовал их для параллелизма. После некоторого поиска в Google, я собрал эту идею, чтобы спрятать все, что я узнал, в одном месте. Цель состоит в том, чтобы выполнить блок в фоновом режиме, а когда он закончится, выполнить другой блок (например, анимацию UIView)...

- (NSOperation *)executeBlock:(void (^)(void))block completion:(void (^)(BOOL finished))completion {

    NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block];

    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
        completion(blockOperation.isFinished);
    }];

    [completionOperation addDependency:blockOperation];
    [[NSOperationQueue mainQueue] addOperation:completionOperation];    

    NSOperationQueue *backgroundOperationQueue = [[NSOperationQueue alloc] init];
    [backgroundOperationQueue addOperation:blockOperation];

    return blockOperation;
}

- (void)testIt {

    NSMutableString *string = [NSMutableString stringWithString:@"tea"];
    NSString *otherString = @"for";

    NSOperation *operation = [self executeBlock:^{
        NSString *yetAnother = @"two";
        [string appendFormat:@" %@ %@", otherString, yetAnother];
    } completion:^(BOOL finished) {
        // this logs "tea for two"
        NSLog(@"%@", string);
    }];

    NSLog(@"keep this operation so we can cancel it: %@", operation);
}

Мои вопросы:

  1. Это работает, когда я запускаю его, но я что-то упустил... скрытая земля? Я не проверял отмену (потому что я не изобрел длинную операцию), но похоже, что это будет работать?
  2. Я обеспокоен тем, что мне нужно квалифицировать свое объявление backgroundOperation, чтобы я мог ссылаться на него в блоке завершения. Компилятор не жалуется, но существует ли там цикл сохранения?
  3. Если бы "строка" была иваром, что бы произошло, если бы я наблюдал это по значению ключа во время работы блока? Или настроить таймер на основном потоке и периодически его регистрировать? Смогу ли я увидеть прогресс? Я объявил бы это атомным?
  4. Если это работает так, как я ожидаю, то это хороший способ скрыть все детали и получить параллелизм. Почему Apple не написал это для меня? Я что-то упустил?

Благодарю.

3 ответа

Решение

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

- (NSOperation *)executeBlock:(void (^)(void))block
                      inQueue:(NSOperationQueue *)queue
                   completion:(void (^)(BOOL finished))completion
{
    NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block];
    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
        completion(blockOperation.isFinished);
    }];
    [completionOperation addDependency:blockOperation];

    [[NSOperationQueue currentQueue] addOperation:completionOperation];
    [queue addOperation:blockOperation];
    return blockOperation;
}

Теперь давайте использовать это:

- (void)tryIt
{
    // Create and configure the queue to enqueue your operations
    backgroundOperationQueue = [[NSOperationQueue alloc] init];

    // Prepare needed data to use in the operation
    NSMutableString *string = [NSMutableString stringWithString:@"tea"];
    NSString *otherString = @"for";

    // Create and enqueue an operation using the previous method
    NSOperation *operation = [self executeBlock:^{
        NSString *yetAnother = @"two";
        [string appendFormat:@" %@ %@", otherString, yetAnother];
    }
    inQueue:backgroundOperationQueue 
    completion:^(BOOL finished) {
        // this logs "tea for two"
        NSLog(@"%@", string);
    }];

    // Keep the operation for later uses
    // Later uses include cancellation ...
    [operation cancel]; 
}

Некоторые ответы на ваши вопросы:

  1. Отмена Обычно вы подкласс NSOperation, чтобы вы могли проверить self.isCancelled и вернуться раньше. Посмотрите эту ветку, это хороший пример. В текущем примере вы не можете проверить, была ли операция отменена из блока, который вы предоставляете для создания NSBlockOperation потому что в то время такой операции еще не было. отмена NSBlockOperation То, что блок вызывается, по-видимому, возможно, но громоздко. NSBlockOperation s для конкретных простых случаев. Если вам нужно отменить, вы лучше подкласс NSOperation:)

  2. Я не вижу здесь проблемы. Хотя обратите внимание на две вещи. a) Я изменил метод do для запуска блока завершения в текущей очереди b) в качестве параметра требуется очередь. Как сказал @Mike Weller, вам лучше поставить background queue так что вам не нужно создавать по одной на каждую операцию, и вы можете выбрать, какую очередь использовать для запуска ваших материалов:)

  3. Я думаю, что да, вы должны сделать stringatomic, Одна вещь, которую вы не должны забывать, это то, что если вы поставите в очередь несколько операций, они могут выполняться не в этом порядке (обязательно), так что вы можете получить очень странное сообщение в вашей string, Если вам нужно запускать одну операцию одновременно, вы можете сделать: [backgroundOperation setMaxConcurrentOperationCount:1]; прежде чем начать ставить в очередь ваши операции. В документах есть примечание, достойное чтения:

    Дополнительные поведения очереди операций Очередь операций выполняет свои объекты операций в очереди в зависимости от их приоритета и готовности. Если все объекты в очереди имеют одинаковый приоритет и готовы к выполнению, когда они помещены в очередь, то есть их метод isReady возвращает YES, они выполняются в том порядке, в котором они были отправлены в очередь. Для очереди, максимальное число одновременных операций которой равно 1, это соответствует последовательной очереди. Однако никогда не следует полагаться на последовательное выполнение объектов операций. Изменения в готовности операции могут изменить результирующий порядок выполнения.

  4. Я думаю, что после прочтения этих строк вы знаете:)

Вы не должны создавать новый NSOperationQueue для каждого executeBlock:completion: вызов. Это дорого, и пользователь этого API не может контролировать, сколько операций может выполняться одновременно.

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

Если вам нужен простой и легкий способ раскрутить блок в фоновом режиме и выполнить код после его завершения, вам, вероятно, лучше сделать несколько простых вызовов GCD с помощью dispatch_* функции. Например:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // do your background work
    // ...

    // now execute the 'completion' code.
    // you often want to dispatch back to the main thread to update the UI
    // For example:

    dispatch_async(dispatch_get_main_queue(), ^{
        // update UI, etc.
        myLabel.text = @"Finished";
    });

});

Нет необходимости настраивать блок для запуска по завершении и добавлять зависимости, подобные этой. NSBlockOperation как все NSOperation подклассы уже имеет completionBlock свойство, которое будет автоматически запускаться после завершения работы блока:

@property(copy) void (^completionBlock)(void);

Блок завершения запускается, когда его блок перемещается в finished государство.

Другие вопросы по тегам