Изучаване на NSBlockOperation

Аз съм голям фен на блоковете, но не съм ги използвал за паралелност. След известно търсене в гугъл събрах тази идея, за да скрия всичко, което научих на едно място. Целта е да се изпълни блок във фонов режим и когато приключи, да се изпълни друг блок (като 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
Но не съвсем правилно, IMHO. В извикването на executeBlock липсва параметърът queue - person decades; 04.02.2014
comment
Извинете, ако въпросът е наивен, но защо да добавяте completionOperation към currentQueue, а не queue? - 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