objective-cnsoperationnsoperationqueue

NSOperation dependency and completionBlock


We're having a simple problem regarding NSOperationQueue, here's a simple operation logic:

self.queue = [[NSOperationQueue alloc] init];

NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"- Running operation A");
    [NSThread sleepForTimeInterval:1.2];
    NSLog(@"- Done operation A");
}];

NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"- Running operation B");
    [NSThread sleepForTimeInterval:2];
    NSLog(@"- Done operation B");
}];

[operationA setCompletionBlock:^{
    NSLog(@"-- Completion Block A");
}];

[operationB setCompletionBlock:^{
    NSLog(@"-- Completion Block B");
}];

[operationB addDependency:operationA];
[self.queue addOperations:@[operationA, operationB] waitUntilFinished:NO];

Here is the final output

2015-12-21 14:59:57.463 SampleProject[18046:310901] - Running operation A
2015-12-21 14:59:58.664 SampleProject[18046:310901] - Done operation A
2015-12-21 14:59:58.664 SampleProject[18046:310900] - Running operation B
2015-12-21 14:59:58.664 SampleProject[18046:310904] -- Completion Block A
2015-12-21 15:00:00.736 SampleProject[18046:310900] - Done operation B
2015-12-21 15:00:00.736 SampleProject[18046:310904] -- Completion Block B

As we can see, the operation B is executed before the operation A's completionBlock. In our real application, we have many operation A and only one operation B that is dependant on all operation A. But then the problem we have is that operationB is launched before the last operation A's completion block has been called, which would normally give information to the operation B.

How would I make operation B to execute after all the operation A's completion blocks?


Solution

  • As you have found in your testing completion blocks are not 'part of the queue' instead they run outside of the Operation Queue (and on another thread). Thus Operation A's completionBlock will run at the same time (ish) as Operation B.

    I suggest you refactor your code to remove all the completion blocks.

    You say you are using the completionBlocks to pass information from operation A's to B, there are two options for this: Give B references to all the A's (not weak) so when B runs it can pick the results from all the A's. Or if keeping all the A's around until B runs is infeasible for some reason, then recreate your completionBlock's as another NSOperation:

    NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
        // do stuff
    }];
    
    NSOperation *operationATail = [NSBlockOperation blockOperationWithBlock:^{
        // do completionBlock stuff
    }];
    
    [operationATail addDependency:operationA];
    [operationB addDependency:operationATail];
    [self.queue addOperations:@[operationA, operationATail, operationB] waitUntilFinished:NO];