Receive notification when NSOperationQueue completes all tasks

NSOperationQueue has waitUntilAllOperationsAreFinished , but I don't want to wait synchronously for it. I just want to hide the progress indicator in the user interface when the queue ends.

What is the best way to accomplish this?

I cannot send notifications from my NSOperation s because I donโ€™t know which one will be the last, and [queue operations] may not be empty yet (or, worse, re-populated) upon receipt of the notification.

+82
asynchronous queue iphone notifications nsoperation
Jun 26 '09 at 13:00
source share
12 answers

Use KVO to monitor the operations property of your queue, then you can find out if your queue is complete by checking [queue.operations count] == 0 .

Somewhere in the file in which you are doing KVO, declare a context for KVO, like this ( more ):

 static NSString *kQueueOperationsChanged = @"kQueueOperationsChanged"; 

When you set up your turn, do the following:

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

Then do this in your 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]; } } 

(It is assumed that your NSOperationQueue is in a property named queue )

At some point, before your object is completely canceled (or when it no longer cares about the state of the queue), you will need to unregister from KVO as follows:

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


Application: iOS 4.0 has the NSOperationQueue.operationCount property, which according to the documents meets the requirements of KVO. This answer will still work in iOS 4.0, so it is still useful for backward compatibility.

+152
Apr 17 2018-10-17T00:
source share

If you expect (or want) something that matches this behavior:

 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> 

You should be aware that if several โ€œshortโ€ operations are added to the queue, you can see this behavior instead (since operations start as part of the queue adding):

 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> 

In my project, I needed to know when the last operation was completed, after a large number of operations were added to the serial NSOperationQueue (i.e. maxConcurrentOperationCount = 1) and only when all of them were completed.

Googling. I found this expression from an Apple developer in response to the question: "Is the serial NSOPAQueue FIFO?" -

If all operations have the same priority (which does not change after the operation is added to the queue), and all operations are always - isReady == YES by the time they are placed in the operation queue, then the sequential NSOperationQueue is FIFO.

Chris Kane Cocoa Framework, Apple

In my case, you can find out when the last operation was added to the queue. Therefore, after adding the last operation, I add another operation to the lower priority queue, which does nothing but send a notification that the queue has been emptied. Given Appleโ€™s operator, this ensures that only one notification is sent only after all operations are completed.

If operations are added in a way that the latter cannot be detected (that is, not deterministic), then I think you need to go with the KVO approaches mentioned above, with the addition of additional protection logic, to try to determine if additional operations can be added .

:)

+17
Nov 17 2018-10-18
source share

How about adding NSOperation, which depends on everyone else, so it will work last?

+13
Sep 07 '09 at 17:11
source share

One option is to use GCD. Refer to this as a reference.

 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 }); 
+11
May 8 '13 at 10:03
source share

This is how I do it.

Configure the queue and register the changes in the operations property:

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

... and the observer (in this case self ) implements:

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

In this example, the โ€œspinnerโ€ is a UIActivityIndicatorView showing that something is happening. Obviously you can change according to ...

+5
Nov 15 '09 at 19:48
source share

With ReactiveCocoa, I believe this works beautifully:

 // 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!"); } }]; 
+3
Dec 29 '15 at 10:19
source share

How about using KVO to monitor the operationCount property of a queue? Then you will hear about it when the queue goes to empty, and also when it stops empty. Working with a progress indicator can be as simple as just doing something like:

 [indicator setHidden:([queue operationCount]==0)] 
+2
Sep 19 '09 at 21:03
source share

Add the last operation, for example:

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

So:

 - (void)method:(id)object withSelector:(SEL)selector{ NSInvocationOperation *callbackOperation = [[NSInvocationOperation alloc] initWithTarget:object selector:selector object:nil]; [callbackOperation addDependency: ...]; [operationQueue addOperation:callbackOperation]; } 
+2
Oct 09 '12 at 19:36
source share

FYI, you can achieve this with the GCD dispatch_group at fast <3>. You can be notified when all tasks are completed.

 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
Jun 15 '17 at 8:49
source share

You can create a new NSThread or execute a selector in the background and wait there. When the NSOperationQueue ends, you can send your own notification.

I am thinking of something like:

 - (void)someMethod { // Queue everything in your operationQueue (instance variable) [self performSelectorInBackground:@selector(waitForQueue)]; // Continue as usual } ... - (void)waitForQueue { [operationQueue waitUntilAllOperationsAreFinished]; [[NSNotificationCenter defaultCenter] postNotification:@"queueFinished"]; } 
0
Jun 26 '09 at 13:13
source share

I use the category for this.

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 

Using

 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 completion here (launched in main thread!) }]; 

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

0
Oct 12 '15 at 15:20
source share

If you use this Operation as a base class, you can pass the whenEmpty {} block to OperationQueue :

 let queue = OOperationQueue() queue.addOperation(op) queue.addOperation(delayOp) queue.addExecution { finished in delay(0.5) { finished() } } queue.whenEmpty = { print("all operations finished") } 
0
Apr 19 '17 at 0:18
source share



All Articles