For example I have 3 objects:
NSOperation *op1 = ...;
NSOperation *op2 = ...;
NSOperation *op3 = ...;
[op3 addDependency:op2];
[op2 addDependency:op1];
NSOperationQueue *queue = ...;
queue.maxConcurrentOperationCount = 1;
[queue addOperations:@[op1, op2, op3] waitUntilFinished:NO];
I could simply add all the operations in correct order. But for example if op2
is cancelled then I should also cancel op3
and I can't fully clear a queue in this case.
My questions:
1)Is it safe to combine such sequences of operations with maxConcurrentOperationCount == 1
?
2)What will the program actually do if I swap around op1
and op2
? (op2
should be performed after op1
but the queue is able to take only one from the operations simultaneously)
P.S. In my application I use AFHTTPRequestOperation
. Its inheritance hierarchy:
AFHTTPRequestOperation
-> AFURLConnectionOperation
-> NSOperation
So I can't simply take other subclass of NSOperation
.
To answer your questions:
maxConcurrentOperations = 1
.op2
, op3
and op1
or op2
, op1
, op3
if you reverse the dependency order of op1
and op2
.Theres nothing tricky in the dependency chain you've specified and NSOperationQueue
can take care of things automatically. You can only really get into trouble if you specify a circular dependency (e.g op3
depends on op1
), or you have an operation that isn't added to the queue, and so can't get executed to satisfy a dependency.
Apple has this to say about cancellation in the NSOperationQueue class reference:
Canceling an operation causes the operation to ignore any dependencies it may have. This behavior makes it possible for the queue to execute the operation’s start method as soon as possible. The start method, in turn, moves the operation to the finished state so that it can be removed from the queue.
All NSOperation subclasses should handle cancellation correctly by first checking to see if it has been cancelled and then immediately finish the operation without performing any actions. If this isn't done then it's a bug, and operations may execute even though they have been cancelled.
(Interestingly this also applies for NSBlockOperation, which I didn't realise. You explicitly need to check self.isCancelled
in the block).
I used CodeRunner on the App Store to try this all out and modified your program slightly. It's reproduced below.
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
NSOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op1"); }];
NSOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op2"); }];
NSOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op3"); }];
[op3 addDependency:op2];
[op2 addDependency:op1];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
[queue addOperations:@[op1, op2, op3] waitUntilFinished:YES];
}
}
For a NSBlockOperation
to refer to itself you need to do this, which is a bit disgusting but looks better in a NSOperation
subclass as you can refer to self
.
__block NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op1 cancelled=%d", op1.cancelled); }];