Recibe una notificación cuando NSOperationQueue finaliza todas las tareas

NSOperationQueue tiene waitUntilAllOperationsAreFinished , pero no quiero esperar sincrónicamente. Solo quiero ocultar el indicador de progreso en la interfaz de usuario cuando termina la cola.

¿Cuál es la mejor manera de lograr esto?

No puedo enviar notificaciones desde mi NSOperation s, porque no sé cuál será la última, y [queue operations] podrían no estar vacías aún (o peor, repoblar) cuando se recibe una notificación.

Use KVO para observar las operations propiedad de su cola, luego puede ver si su cola se ha completado al buscar [queue.operations count] == 0 .

En algún lugar del archivo en el que está haciendo el KVO, declare un contexto para KVO de esta manera ( más información ):

 static NSString *kQueueOperationsChanged = @"kQueueOperationsChanged"; 

Cuando configure su cola, haga esto:

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

Luego haga esto en su 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]; } } 

(Esto supone que su NSOperationQueue encuentra en una propiedad denominada queue )

En algún momento antes de que su objeto negocie por completo (o cuando deje de preocuparse por el estado de la cola), tendrá que cancelar el registro de KVO de esta manera:

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

Adición: iOS 4.0 tiene una propiedad NSOperationQueue.operationCount , que de acuerdo con los documentos es compatible con KVO. Esta respuesta todavía funcionará en iOS 4.0, por lo que sigue siendo útil para la compatibilidad con versiones anteriores.

Si espera (o desea) algo que coincida con este comportamiento:

 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  

Debe tener en cuenta que si se agregan varias operaciones “cortas” a una cola, puede ver este comportamiento en su lugar (porque las operaciones se inician como parte de la cola).

 t=0 add an operation to the queue. queuecount == 1 t=1 operation completes, queuecount decrements to 0  t=2 add an operation to the queue. queuecount == 1 t=3 operation completes, queuecount decrements to 0  t=4 add an operation to the queue. queuecount == 1 t=5 operation completes, queuecount decrements to 0  

En mi proyecto necesitaba saber cuándo se completó la última operación, después de que se agregaron un gran número de operaciones a un NSOperationQueue en serie (es decir, maxConcurrentOperationCount = 1) y solo cuando se completaron todas.

Google encontró esta afirmación de un desarrollador de Apple en respuesta a la pregunta “¿Es un NSOP de serial FIFO?” –

Si todas las operaciones tienen la misma prioridad (que no se cambia después de que la operación se agrega a una cola) y todas las operaciones siempre son – isReady == YES para cuando se colocan en la cola de operaciones, entonces una NSOperationQueue en serie es FIFO.

Marcos de cocoa de Chris Kane, Apple

En mi caso, es posible saber cuándo se agregó la última operación a la cola. Entonces, después de agregar la última operación, agrego otra operación a la cola, de menor prioridad, que no hace más que enviar la notificación de que la cola se ha vaciado. Dada la statement de Apple, esto garantiza que solo se envíe un único aviso después de que se hayan completado todas las operaciones.

Si las operaciones se agregan de una manera que no permite detectar el último (es decir, no determinista), entonces creo que debe seguir los enfoques de KVO mencionados anteriormente, con la lógica de protección adicional añadida para tratar de detectar si operaciones pueden ser agregadas

🙂

¿Qué tal si agregas un NSOperation que depende de todos los demás para que funcione por última vez?

Una alternativa es usar GCD. Refiérase a esto como referencia.

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

Así es como lo hago.

Configure la cola y regístrese para cambios en la propiedad de operaciones:

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

… y el observador (en este caso self ) implementa:

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

En este ejemplo, “spinner” es un UIActivityIndicatorView que muestra que algo está sucediendo. Obviamente puedes cambiar para adaptarse …

¿Qué hay de usar KVO para observar la propiedad operationCount de la cola? Luego escucharía sobre eso cuando la cola se vaciara y también cuando dejara de estar vacía. Tratar con el indicador de progreso puede ser tan simple como hacer algo como:

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

Agregue la última operación como:

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

Asi que:

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

Con ReactiveObjC encuentro que esto funciona muy bien:

 // 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!"); } }]; 

FYI, puedes lograrlo con GCD dispatch_group en swift 3 . Puede recibir una notificación cuando finalicen todas las tareas.

 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") } 

Puede crear un nuevo NSThread o ejecutar un selector en segundo plano y esperar ahí. Cuando finalice NSOperationQueue , puede enviar una notificación propia.

Estoy pensando en algo como:

 - (void)someMethod { // Queue everything in your operationQueue (instance variable) [self performSelectorInBackground:@selector(waitForQueue)]; // Continue as usual } ... - (void)waitForQueue { [operationQueue waitUntilAllOperationsAreFinished]; [[NSNotificationCenter defaultCenter] postNotification:@"queueFinished"]; } 

Si usa esta Operación como su clase base, podría pasar el bloque whenEmpty {} a la OperationQueue :

 let queue = OOperationQueue() queue.addOperation(op) queue.addOperation(delayOp) queue.addExecution { finished in delay(0.5) { finished() } } queue.whenEmpty = { print("all operations finished") } 

Estoy usando una categoría para hacer esto.

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 

Uso :

 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!) }]; 

Fuente: https://gist.github.com/artemstepanenko/7620471

Sin KVO

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