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.
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:
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With