Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do NSOperations and their completionBlocks run concurrently?

I've got a bunch of NSOperations added to a NSOperationQueue. The operation queue has the maxConcurrentOperationCount set to 1, so that the NSOperations run one after the other.

Now, in the completionBlock of a NSOperation I want to cancel all pending NSOperations by calling cancelAllOperations on the NSOperationQueue.

Is it safe to do this? Can I be sure that the start-method of the next operation is called only after the completionBlock of the previous operation has been fully executed? Or do the completionBlock of the previous operation and the task of the current operation run concurrently?

The reason why I'm asking: I use AFNetworking to execute a batch of AFHTTPRequestOperations and want to perform one request only if all previous requests of the batch were successful.

like image 950
Tom Avatar asked Jul 10 '12 12:07

Tom


2 Answers

My findings below no longer seem to be true. I've re-run the tests on iOS 8 and iOS 9 and the completion block of an operation always runs concurrently with the next operation. Currently, I don't see a way to make an operation wait for the previous completion block to finish.


I just tried this scenario in a sample project. Here is the result:

If the NSOperationQueue's maxConcurrentOperationCount is set to 1, an NSOperation's completionBlock and the next NSOperation in the queue run simultaneously.

But, if every NSOperation is linked to its previous operation by calling addDependency:, the execution of an operation waits until the previous operation's completionBlock has finished.

So, if you want to cancel the next operation in the completionBlock of the current operation and be sure that it is cancelled before it is started, you have to set dependencies between the NSOperations by calling addDependency:

like image 177
Tom Avatar answered Dec 13 '22 14:12

Tom


I came up with another seemingly better way to ensure that an operaion is executed only if certain conditions (based on the results of previously finished operations) are met, else, the operation is cancelled.

One important consideration here is that the condition check for running an operation should not be coded inside the operation subclass, thus allowing the operation subclass to be poratble across different scenarios and apps.

Solution: - Have a condition block property inside the subclass, and set whatever condition form where the operation is instantiated. - Override "isReady" getter of the NSOperation subclass, check the condition there, and thus determine if its ready for execution. - If [super isReady] is YES, which means the dependent operations are all finished, then evaluate the necessary condition. - If the condition check is passed, return YES. Else, set isCancelled to YES and return YES for isReady

Code: In the interface file have the block property:

typedef BOOL(^ConditionBlock)(void);

@property (copy) ConditionBlock conditionBlock;

In the implementation, override isReady, and cancelled:

@implementation ConditionalOperation

- (BOOL)isReady {
        if([super isReady]) {
            if(self.conditionBlock) {
                if(!self.conditionBlock()) {
                    [self setCancelled:YES];
                }
                return YES;
            } else {
                return YES;
            }
        } else {
            return NO;
        }
    }
like image 45
Kabeer Avatar answered Dec 13 '22 16:12

Kabeer