Subclassification of NSOperation will be simultaneous and abolition

I cannot find good documentation on how to subclass NSOperation at the same time, and also support undo. I have read Apple docs, but I cannot 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 do not understand why useSelectorOnMainThread: is used. This will prevent my operation from starting at the same time.

Also, when I comment on this line, I run my operations at the same time. However, the isCancelled flag isCancelled not change, although I called cancelAllOperations .

+48
iphone performselector nsoperation nsoperationqueue
04 Oct '10 at 22:29
source share
6 answers

Well, as I understand it, you have two questions:

  • Do you need the performSelectorOnMainThread: segment that appears in the comments of your code? What does this code do?

  • Why _isCancelled n't the _isCancelled flag change when cancelAllOperations called in the NSOperationQueue that contains this operation?

Let's handle it. I assume your subclass of NSOperation is called MyOperation , just for the convenience of explanation. I will explain what you misunderstand, and then give a corrected example.

1. Concurrent NSOperations

In most cases, you will use NSOperation with NSOperationQueue , and from your code, it looks like what you are doing. In this case, your MyOperation will always run in the background thread, regardless of what the -(BOOL)isConcurrent method returns, since NSOperationQueue explicitly designed to perform operations in the background.

As a rule, you do not need to override the method -[NSOperation start] , because by default it just calls the -main method. This is a method that you must override. By default, the -start method already processes the isExecuting and isFinished for you at appropriate times.

So, if you want NSOperation run in the background, just override the -main method and place it on the NSOperationQueue .

performSelectorOnMainThread: in your code will cause each instance of MyOperation always perform its task in the main thread. Since only one piece of code can run downstream at a time, this means that no other MyOperation can be started. The goal of NSOperation and NSOperationQueue is to do something in the background.

The only time you want to force things into the main thread is when you update the user interface. If you need to update the interface when MyOperation ends, that is, when you should use performSelectorOnMainThread: I will show how to do this in my example below.

2. Cancel NSOperation

-[NSOperationQueue cancelAllOperations] calls the method -[NSOperation cancel] , which calls subsequent calls -[NSOperation isCancelled] to return YES . However , you did two things to make it ineffective.

  • You use @synthesize isCancelled to override the NSOperation -isCancelled method. There is no reason for this. NSOperation already implements -isCancelled perfectly acceptable way.

  • You check your own _isCancelled instance variable to determine if the operation has been canceled. NSOperation ensures that [self isCancelled] will return YES if the operation was canceled. It does not guarantee that your custom setter method will be called, or that your own instance variable has been updated. You must check [self isCancelled]

What should you do

Title:

 // MyOperation.h @interface MyOperation : NSOperation { } @end 

And implementation:

 // MyOperation.m @implementation MyOperation - (void)main { if ([self isCancelled]) { NSLog(@"** operation cancelled **"); } // Do some work here NSLog(@"Working... working....") if ([self isCancelled]) { NSLog(@"** operation cancelled **"); } // Do any clean-up work here... // If you need to update some UI when the operation is complete, do this: [self performSelectorOnMainThread:@selector(updateButton) withObject:nil waitUntilDone:NO]; NSLog(@"Operation finished"); } - (void)updateButton { // Update the button here } @end 

Note that you do not need to do anything with isExecuting , isCancelled or isFinished . All of them are processed automatically for you. Just override the -main method. It is easy.

(Note: technically, this is not a “parallel” NSOperation , in the sense that -[MyOperation isConcurrent] will return NO , as implemented above, but will be executed in the background thread. isConcurrent really needs to be called -willCreateOwnThread , as this is a more accurate description of the intent of the method .)

+106
Nov 29 '10 at 4:24
source share

I know this is an old question, but I have been studying this recently and have come across the same examples and had the same doubts.

If all your work can be performed synchronously inside the main method, you do not need a parallel operation that does not cancel the beginning, just do your work and return from the main when it is done.

However, if your load is asynchronous in nature - for example, when loading NSURLConnection, you must run a subclass. When your start method returns, the operation is not finished yet. It will be considered completed by NSOperationQueue when you manually send KVO notifications to the isFinished and isExecuting flags (for example, after the completion or failure to load the async URL).

Finally, you might want to send a start to the main thread when the async work task that you want to run requires listening on the first thread. Since the work itself is asynchronous, it will not limit your concurrency, but starting work in a workflow may not have a proper working environment.

+2
Oct 18 '13 at 14:06 on
source share

Great answer @BJHomer deserves an update.

Parallel operations should override the start method instead of main .

As stated in the Apple Documentation :

If you are creating a parallel operation, you need to override the following methods and properties as little as possible:

  • start
  • asynchronous
  • executing
  • finished

Correct implementation also requires overriding cancel . Subclassing thread-safe and getting semantics right is also quite difficult.

Thus, I put a complete and working subclass as a proposal implemented in Swift in a code review. Comments and suggestions are welcome.

This class can easily be used as a base class for your custom operations class.

+1
Oct. 15 '15 at 11:42
source share

Take a look at ASIHTTPRequest . This is an HTTP wrapper class built on top of NSOperation as a subclass and seems to implement them. Please note that as of mid-2011, the developer recommends not using ASI for new projects.

0
Nov 28 '10 at 21:23
source share

Regarding the property definition, it is canceled "(or defining" _cancelled "iVAR) in a subclass of NSOperation, usually it is NOT necessary. Just because when USER starts the cancellation, user code should always notify KVO observers that your work has now been completed with its work In other words, isCancelled => isFinished.

In particular, when an NSOperation object depends on the completion of other work objects, it tracks the path of the isFinished key for these objects. Thus, the inability to generate a notification of completion ( in case of cancellation ) may interfere with other operations in your application.




BTW, @BJ Homer replies: “The isConcurrent method really should be named -willCreateOwnThread” makes a LOT sense !

Because if you DO NOT override the start method, simply manually call the default-start-method NSOperation-Object method, the default thread-call itself is synchronous; therefore, NSOperation-Object is only a non-competitive operation.

However, if you override the start method, inside the implementation of the start method, the user code must spawn a separate thread ... etc., then you successfully violate the "call-thread default, synchronous" restriction, so NSOperation-Object becomes a parallel operation, it can start asynchronously after that.

0
Jul 13 '14 at 0:59
source share

This blog post is:

http://www.dribin.org/dave/blog/archives/2009/09/13/snowy_concurrent_operations/

explains why you might need:

 if (![NSThread isMainThread]) { [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; return; } 

in your start method.

-2
01 Oct '12 at 22:15
source share



All Articles