Изучение 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. Если бы «строка» была ivar, что бы произошло, если бы я наблюдал за ней во время выполнения блока? Или установить таймер в основном потоке и периодически его регистрировать? Смогу ли я увидеть прогресс? Мог бы я объявить его атомным?
  4. Если это сработает так, как я ожидал, то это хороший способ скрыть все детали и добиться параллелизма. Почему Apple не написала это для меня? Я упустил что-то важное?

Спасибо.


person danh    schedule 13.06.2012    source источник
comment
Вы рассматривали возможность использования GCD? Или это чисто обучающее упражнение? Последовательная очередь звучит именно так, как вы ищете.   -  person borrrden    schedule 13.06.2012


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

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

  3. # P6 #
    # P7 #
  4. Думаю, прочитав эти строчки, вы понимаете :)

person nacho4d    schedule 06.07.2012
comment
Отличный ответ. Большое спасибо. - person danh; 06.07.2012
comment
Но не совсем правильно, ИМХО. В вызове executeBlock отсутствует параметр очереди - person decades; 04.02.2014
comment
Извините, если вопрос наивный, но зачем добавлять завершениеOperation в currentQueue, а не в очередь? - person user3752049; 25.06.2015

Вы не должны создавать новый 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";
    });

});
person Mike Weller    schedule 13.06.2012
comment
@danh, по поводу отмены, пожалуйста, посмотрите объявление NSOperationsQueue и метод cancelAllOperations;) - person Łukasz Szpyrka; 26.07.2014

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

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

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

person nevan king    schedule 27.07.2018