Demystify NSOperation: concurrent vs non-concurrent and async pattern

asynchronous, nsoperation, nsoperationqueue, nsurlconnection, objective-c

Solution

In my opinion, the `isConcurrent` property of `NSOperation` is confusingly named. It really means "is-asynchronous". That is, when its `-start` is invoked, does it return quickly whether or not the operation has run to completion (asynchronous)? Or does it not return until the operation has run to completion (synchronous)?

As Apple's docs state, this doesn't much matter when an operation is queued to an `NSOperationQueue`, since the queue invokes it on a worker thread, regardless. If it is synchronous, then that worker thread will be devoted to that operation until it completes. If it is asynchronous, then `-start` will return before the operation has completed and the worker thread can go on to do other work.

The question is, how does an asynchronous `-start` method make sure the work of the operation continues? That could entail spawning a separate thread to do the work, but that's silly. Better to let `NSOperationQueue` handle the threading.

More likely, it uses a run loop source that's driven by external events. `NSURLConnection` in its asynchronous mode is that sort of thing. But, in that case, it has to be sure the thread on which it schedules the run loop source a) will stick around, and b) will run its run loop. The worker threads of `NSOperationQueue` can't be relied upon to do either.

Again, you could create your own thread for each such operation specifically to stick around and run its run loop, but that's unnecessary and, again, offers no advantage over leaving the operation synchronous and queuing it.

One thread which you already know will stick around and run its run loop is the main thread. So, it is often best to schedule the run loop source on the main thread's run loop. The only thing to be careful of is that, in response to the external events that trigger the run loop source's handlers, you don't do any long-running work on the main thread. So, for example, when `NSURLConnection` calls your delegate methods with received data, you don't do expensive calculations on that data – or, if you have to, move that expensive computation to another thread.

Another possibility, a middle ground, is to create one thread of your own to be the worker for many asynchronous operations. So, rather than using the main thread or a thread per operation, you use a single thread whose job is just to sit around parked in its run loop. All of your asynchronous operations would schedule themselves on that thread's run loop. There's not really much need for or advantage to this approach, though.

Problem

Yes, I know. There are a lot of questions and answers about the `NSOperation` world but I'm still having some doubts. Il' try to explain my doubts with a two parts question. They are related each other. In the SO post nsoperationqueue-and-concurrent-vs-non-concurrent, Darren wrote that A "concurrent" operation is concurrent on its own; it doesn't need NSOperationQueue to create a thread for it. But searching a little bit, I've found that a `NSOperation`, even if it is declared concurrent (by means of overriding the `isConcurrent` method such as it returns `YES`), can be added to `NSOperationQueue`. What does this mean? If I add a concurrent `NSOperation` to a queue, what is going on under the hood? On the contrary, what happens if I use a concurrent operation as is (without adding it to a queue)? The note taken from Apple doc is clear: ...operation queues ignore the value returned by isConcurrent and always call the start method of your operation from a separate thread. ...In general, if you are always using operations with an operation queue, there is no reason to make them concurrent. Then, I'm really interested about using async pattern in `NSOperation`. I've found a good tutorial by Dave Dribin (concurrent operations). I got the overall meaning of his post. You cannot use an async pattern (e.g. using an async `NSURLConnection` request) since delegates could not be called. When the `main` finishes the operation is removed. The solution is to override the `start` method to control the operation lifecycle...And dealing with run loops could be a pain. Now, trying to understand his post, my doubt is about the need to run the `start` method in the main thread. ``` - (void)start { if (![NSThread isMainThread]) { [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; return; } // other code here... } ``` When dealing with asynchronous APIs, we can begin the asynchronous call on the main thread in start and keep the operation running until it finishes. Could you explain me why? Thank you in advance.

Original source

Related problems