Обучение NSBlockOperation - программирование
Подтвердить что ты не робот

Обучение NSBlockOperation

Я большой поклонник блоков, но не использовал их для concurrency. После некоторого поиска я собрал эту идею, чтобы скрыть все, что я узнал в одном месте. Цель состоит в том, чтобы выполнить блок в фоновом режиме, и когда он закончен, выполните другой блок (например, анимацию 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);
}

Мои вопросы:

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

Спасибо.

4b9b3361

Ответ 1

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

- (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]; 
}

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

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

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

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

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

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

Ответ 2

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

});