I am unable to find good documentation about how to subclass NSOperation to be concurrent and also to support cancellation. I read the Apple docs, but I am unable to find an “official” example.
Here is my source code :
@synthesize isExecuting = _isExecuting;
@synthesize isFinished = _isFinished;
@synthesize isCancelled = _isCancelled;
- (BOOL)isConcurrent
{
return YES;
}
- (void)start
{
/* WHY SHOULD I PUT THIS ?
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
return;
}
*/
[self willChangeValueForKey:@"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
if (_isCancelled == YES)
{
NSLog(@"** OPERATION CANCELED **");
}
else
{
NSLog(@"Operation started.");
sleep(1);
[self finish];
}
}
- (void)finish
{
NSLog(@"operationfinished.");
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
if (_isCancelled == YES)
{
NSLog(@"** OPERATION CANCELED **");
}
}
In the example I found, I don’t understand why performSelectorOnMainThread: is used. It would prevent my operation from running concurrently.
Also, when I comment out that line, I get my operations running concurrently. However, the isCancelled flag is not modified, even though I have called cancelAllOperations.
Okay, so as I understand it, you have two questions:
Do you need the
performSelectorOnMainThread:segment that appears in comments in your code? What does that code do?Why is the
_isCancelledflag is not modified when you callcancelAllOperationson theNSOperationQueuethat contains this operation?Let’s deal with these in order. I’m going to assume that your subclass of
NSOperationis calledMyOperation, just for ease of explanation. I’ll explain what you’re misunderstanding and then give a corrected example.1. Running NSOperations concurrently
Most of the time, you’ll use
NSOperations with anNSOperationQueue, and from your code, it sounds like that’s what you’re doing. In that case, yourMyOperationwill always be run on a background thread, regardless of what the-(BOOL)isConcurrentmethod returns, sinceNSOperationQueues are explicitly designed to run operations in the background.As such, you generally do not need to override the
-[NSOperation start]method, since by default it simply invokes the-mainmethod. That is the method you should be overriding. The default-startmethod already handles settingisExecutingandisFinishedfor you at the appropriate times.So if you want an
NSOperationto run in the background, simply override the-mainmethod and put it on anNSOperationQueue.The
performSelectorOnMainThread:in your code would cause every instance ofMyOperationto always perform its task on the main thread. Since only one piece of code can be running on a thread at a time, this means that no otherMyOperations could be running. The whole purpose ofNSOperationandNSOperationQueueis to do something in the background.The only time you want to force things onto the main thread is when you’re updating the user interface. If you need to update the UI when your
MyOperationfinishes, that is when you should useperformSelectorOnMainThread:. I’ll show how to do that in my example below.2. Cancelling an NSOperation
-[NSOperationQueue cancelAllOperations]calls the-[NSOperation cancel]method, which causes subsequent calls to-[NSOperation isCancelled]to returnYES. However, you have done two things to make this ineffective.You are using
@synthesize isCancelledto override NSOperation’s-isCancelledmethod. There is no reason to do this.NSOperationalready implements-isCancelledin a perfectly acceptable manner.You are checking your own
_isCancelledinstance variable to determine whether the operation has been cancelled.NSOperationguarantees that[self isCancelled]will returnYESif the operation has been cancelled. It does not guarantee that your custom setter method will be called, nor that your own instance variable is up to date. You should be checking[self isCancelled]What you should be doing
The header:
And the implementation:
Note that you do not need to do anything with
isExecuting,isCancelled, orisFinished. Those are all handled automatically for you. Simply override the-mainmethod. It’s that easy.(A note: technically, this is not a “concurrent”
NSOperation, in the sense that-[MyOperation isConcurrent]would returnNOas implemented above. However, it will be run on a background thread. TheisConcurrentmethod really should be named-willCreateOwnThread, as that is a more accurate description of the method’s intention.)