Получайте уведомления, когда NSOperationQueue завершит все задачи

StackOverflow https://stackoverflow.com/questions/1049001

Вопрос

NSOperationQueue имеет waitUntilAllOperationsAreFinished, но я не хочу ждать этого синхронно.Я просто хочу скрыть индикатор выполнения в пользовательском интерфейсе, когда очередь завершится.

Каков наилучший способ добиться этого?

Я не могу отправлять уведомления со своего NSOperations, потому что я не знаю, какой из них будет последним, и [queue operations] возможно, он еще не пуст (или, что еще хуже, повторно заполнен) на момент получения уведомления.

Это было полезно?

Решение

Используйте KVO для наблюдения за operations свойство вашей очереди, то вы можете узнать, завершилась ли ваша очередь, проверив [queue.operations count] == 0.

Где-нибудь в файле, в котором вы выполняете KVO, объявите контекст для KVO следующим образом (больше информации):

static NSString *kQueueOperationsChanged = @"kQueueOperationsChanged";

Когда вы настраиваете очередь, сделайте следующее:

[self.queue addObserver:self forKeyPath:@"operations" options:0 context:&kQueueOperationsChanged];

Тогда сделайте это в своем observeValueForKeyPath:

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 
                         change:(NSDictionary *)change context:(void *)context
{
    if (object == self.queue && [keyPath isEqualToString:@"operations"] && context == &kQueueOperationsChanged) {
        if ([self.queue.operations count] == 0) {
            // Do something here when your queue has completed
            NSLog(@"queue has completed");
        }
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object 
                               change:change context:context];
    }
}

(Это при условии, что ваш NSOperationQueue находится в свойстве с именем queue)

В какой-то момент, прежде чем ваш объект полностью освободится от выделения памяти (или когда он перестанет заботиться о состоянии очереди), вам нужно будет отменить регистрацию в KVO следующим образом:

[self.queue removeObserver:self forKeyPath:@"operations" context:&kQueueOperationsChanged];


Приложение:iOS 4.0 имеет NSOperationQueue.operationCount имущество, которое по документам соответствует требованиям КВО.Однако этот ответ по-прежнему будет работать в iOS 4.0, поэтому он по-прежнему полезен для обратной совместимости.

Другие советы

Если вы ожидаете (или желаете) чего-то, что соответствует этому поведению:

t=0 add an operation to the queue.  queueucount increments to 1
t=1 add an operation to the queue.  queueucount increments to 2
t=2 add an operation to the queue.  queueucount increments to 3
t=3 operation completes, queuecount decrements to 2
t=4 operation completes, queuecount decrements to 1
t=5 operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>

Вы должны знать, что если в очередь добавляется несколько "коротких" операций, вы можете увидеть это поведение вместо этого (поскольку операции запускаются как часть добавления в очередь):

t=0  add an operation to the queue.  queuecount == 1
t=1  operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>
t=2  add an operation to the queue.  queuecount == 1
t=3  operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>
t=4  add an operation to the queue.  queuecount == 1
t=5  operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>

В моем проекте мне нужно было знать, когда завершена последняя операция, после того, как большое количество операций было добавлено в последовательный NSOperationQueue (т. Е. maxConcurrentOperationCount= 1) и только тогда, когда все они были завершены.

Погуглив, я нашел это заявление от разработчика Apple в ответ на вопрос "является ли последовательный NSOperationQueue FIFO?" --

Если все операции имеют одинаковый приоритет (который не изменяется после добавления операции в очередь) и все операции всегда - Готовы ==ДА к моменту их помещения в очередь операций, то последовательный NSOperationQueue - это FIFO.

Крис Кейн Фреймворки Cocoa, Apple

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

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

:)

Как насчет добавления NSOperation, которая зависит от всех остальных и будет выполняться последней?

Одной из альтернатив является использование GCD.Ссылаться на этот в качестве ссылки.

dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,queue,^{
 NSLog(@"Block 1");
 //run first NSOperation here
});

dispatch_group_async(group,queue,^{
 NSLog(@"Block 2");
 //run second NSOperation here
});

//or from for loop
for (NSOperation *operation in operations)
{
   dispatch_group_async(group,queue,^{
      [operation start];
   });
}

dispatch_group_notify(group,queue,^{
 NSLog(@"Final block");
 //hide progress indicator here
});

Вот как я это делаю.

Настройте очередь и зарегистрируйте изменения в свойстве операций:

myQueue = [[NSOperationQueue alloc] init];
[myQueue addObserver: self forKeyPath: @"operations" options: NSKeyValueObservingOptionNew context: NULL];

...и наблюдатель (в данном случае self) реализует:

- (void) observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context {

    if (
        object == myQueue
        &&
        [@"operations" isEqual: keyPath]
    ) {

        NSArray *operations = [change objectForKey:NSKeyValueChangeNewKey];

        if ( [self hasActiveOperations: operations] ) {
            [spinner startAnimating];
        } else {
            [spinner stopAnimating];
        }
    }
}

- (BOOL) hasActiveOperations:(NSArray *) operations {
    for ( id operation in operations ) {
        if ( [operation isExecuting] && ! [operation isCancelled] ) {
            return YES;
        }
    }

    return NO;
}

В этом примере «спиннер» — это UIActivityIndicatorView показывая, что что-то происходит.Конечно, можно переодеться по своему вкусу...

А как насчет использования KVO для наблюдения за operationCount свойство очереди?Затем вы услышите об этом, когда очередь опустеет, а также когда она перестанет быть пустой.Работа с индикатором прогресса может быть такой же простой, как просто сделать что-то вроде:

[indicator setHidden:([queue operationCount]==0)]

Добавьте последнюю операцию, например:

NSInvocationOperation *callbackOperation = [[NSInvocationOperation alloc] initWithTarget:object selector:selector object:nil];

Так:

- (void)method:(id)object withSelector:(SEL)selector{
     NSInvocationOperation *callbackOperation = [[NSInvocationOperation alloc] initWithTarget:object selector:selector object:nil];
     [callbackOperation addDependency: ...];
     [operationQueue addOperation:callbackOperation]; 

}

С РеактивныйObjC Я считаю, что это прекрасно работает:

// skip 1 time here to ignore the very first call which occurs upon initialization of the RAC block
[[RACObserve(self.operationQueue, operationCount) skip:1] subscribeNext:^(NSNumber *operationCount) {
    if ([operationCount integerValue] == 0) {
         // operations are done processing
         NSLog(@"Finished!");
    }
}];

К вашему сведению, вы можете добиться этого с помощью GCD диспетчерская_группа в быстрый 3.Вы можете получить уведомление, когда все задачи будут завершены.

let group = DispatchGroup()

    group.enter()
    run(after: 6) {
      print(" 6 seconds")
      group.leave()
    }

    group.enter()
    run(after: 4) {
      print(" 4 seconds")
      group.leave()
    }

    group.enter()
    run(after: 2) {
      print(" 2 seconds")
      group.leave()
    }

    group.enter()
    run(after: 1) {
      print(" 1 second")
      group.leave()
    }


    group.notify(queue: DispatchQueue.global(qos: .background)) {
      print("All async calls completed")
}

Для этого я использую категорию.

NSOperationQueue+Completion.h

//
//  NSOperationQueue+Completion.h
//  QueueTest
//
//  Created by Artem Stepanenko on 23.11.13.
//  Copyright (c) 2013 Artem Stepanenko. All rights reserved.
//

typedef void (^NSOperationQueueCompletion) (void);

@interface NSOperationQueue (Completion)

/**
 * Remarks:
 *
 * 1. Invokes completion handler just a single time when previously added operations are finished.
 * 2. Completion handler is called in a main thread.
 */

- (void)setCompletion:(NSOperationQueueCompletion)completion;

@end

NSOperationQueue+Completion.m

//
//  NSOperationQueue+Completion.m
//  QueueTest
//
//  Created by Artem Stepanenko on 23.11.13.
//  Copyright (c) 2013 Artem Stepanenko. All rights reserved.
//

#import "NSOperationQueue+Completion.h"

@implementation NSOperationQueue (Completion)

- (void)setCompletion:(NSOperationQueueCompletion)completion
{
    NSOperationQueueCompletion copiedCompletion = [completion copy];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self waitUntilAllOperationsAreFinished];

        dispatch_async(dispatch_get_main_queue(), ^{
            copiedCompletion();
        });
    });
}

@end

Применение:

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    // ...
}];

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    // ...
}];

[operation2 addDependency:operation1];

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation1, operation2] waitUntilFinished:YES];

[queue setCompletion:^{
    // handle operation queue's completion here (launched in main thread!)
}];

Источник: https://gist.github.com/artemstepanенко/7620471

Вы можете создать новый NSThread, или выполните селектор в фоновом режиме и подождите там.Когда NSOperationQueue завершается, вы можете отправить собственное уведомление.

Я думаю о чем-то вроде:

- (void)someMethod {
    // Queue everything in your operationQueue (instance variable)
    [self performSelectorInBackground:@selector(waitForQueue)];
    // Continue as usual
}

...

- (void)waitForQueue {
    [operationQueue waitUntilAllOperationsAreFinished];
    [[NSNotificationCenter defaultCenter] postNotification:@"queueFinished"];
}

Если вы используете это Операция в качестве базового класса вы можете передать whenEmpty {} блокировать до Операционная очередь:

let queue = OOperationQueue()
queue.addOperation(op)
queue.addOperation(delayOp)

queue.addExecution { finished in
    delay(0.5) { finished() }
}

queue.whenEmpty = {
    print("all operations finished")
}

Без КВО

private let queue = OperationQueue()

private func addOperations(_ operations: [Operation], completionHandler: @escaping () -> ()) {
    DispatchQueue.global().async { [unowned self] in
        self.queue.addOperations(operations, waitUntilFinished: true)
        DispatchQueue.main.async(execute: completionHandler)
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top