Получать уведомления, когда NSOperationQueue завершает все задачи - PullRequest
89 голосов
/ 26 июня 2009

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

Какой лучший способ сделать это?

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

Ответы [ 13 ]

164 голосов
/ 17 апреля 2010

Используйте 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, которое в соответствии с документацией является KVO-совместимым. Однако этот ответ будет по-прежнему работать в iOS 4.0, поэтому он по-прежнему полезен для обратной совместимости.

20 голосов
/ 17 ноября 2010

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

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?" -

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

Крис Кейн Каркасы какао, яблоко

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

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

:)

17 голосов
/ 07 сентября 2009

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

12 голосов
/ 08 мая 2013

Одной из альтернатив является использование 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
});
5 голосов
/ 15 ноября 2009

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

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

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

В этом примере "spinner" - это UIActivityIndicatorView, показывающий, что что-то происходит. Очевидно, вы можете изменить, чтобы удовлетворить ...

2 голосов
/ 29 декабря 2015

С ReactiveObjC Я считаю, что это прекрасно работает:

// 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!");
    }
}];
2 голосов
/ 09 октября 2012

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

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

}
2 голосов
/ 20 сентября 2009

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

[indicator setHidden:([queue operationCount]==0)]
1 голос
/ 15 июня 2017

К вашему сведению, вы можете добиться этого с помощью GCD dispatch_group in swift 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")
}
1 голос
/ 12 октября 2015

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

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/artemstepanenko/7620471

...