8

I am diving a bit deeper into concurrency and have been reading extensively about GCD and NSOperation. However, a lot of posts like the canonic answer on SO are several years old.

It seemed to me that NSOperation main advantages used to be, at the cost of some performance:

  • "the way to go" generally for more than a simple dispatch as the highest level abstraction (built atop of GCD)
  • to make task manipulation (cancellation, etc.) a lot easier
  • to easily set up dependencies between tasks

Given GCD's DispatchWorkItem & block cancellation / DispatchGroup / qos in particular, is there really an incentive (cost-performance wise) to use NSOperation anymore for concurrency apart from cases where you need to be able to cancel a task when it began executing or query the task state ?

Apple seems to put a lot more emphasis on GCD, at least in their WWDC (granted it's more recent than NSOperation).

Community
  • 1
  • 1
Herakleis
  • 513
  • 5
  • 21

2 Answers2

4

I see them each still having their own purpose. I just recently rewatched the 2015 WWDC talk about this (Advanced NSOperations), and I see two main points here.

Run Time & User Interaction

From the talk:

NSOperations run for a little bit longer than you would expect a block to run, so blocks usually take a few nanoseconds, maybe at most a millisecond, to execute.

NSOperations, on the other hand, can be much longer, for anywhere from a couple of milliseconds to even several minutes

The example they talk about is in the WWDC app, where there exists an NSOperation that has a dependency on having a logged in user. The dependency NSOperation presents a login view controller and waits for the user to authenticate. Once finished, that NSOperation finishes and the NSOperationQueue resumes it's work. I don't think you'd want to use GCD for this scenario.

Subclassing

Since NSOperations are just classes, you can subclass them to get more reusability out of them. This isn't possible with GCD.

Example: (Using the WWDC login scenario from above)

You have many NSOperations in your code base that are associated with a user interaction that requires them to be authenticated. (Liking a video, in this example.) You could extend NSOperation to create an AuthenticatedOperation, then have all those NSOperations extend this new class.

Stephen
  • 741
  • 8
  • 18
2

First off, NSOperationQueue let you enqueue operations, that is, some sort of asynchronous operations with a start method, a cancel method and a few observable properties, while with a dispatch queue one can submit a block or a closure or a function to a dispatch queue, which will be then executed.

An "Operation" is semantically fundamentally different than a block (or closure, function). An operation has an underlying asynchronous task, while a block (closure or functions) is just that.

What comes close to an NSOperation, though, is an asynchronous function, e.g.:

func asyncTask(param: Param, completion: (T?, Error?) ->())

Now with Futures we can define the same asynchronous function like:

func asyncTask(param: Param) -> Future<T>

which makes such asynchronous functions quite handy.

Since futures have combinator functions like map and flatMap and so on, we can quite easily "emulate" the "dependency" feature of NSOperation, just in a more powerful, more concise and more comprehensible way.

We can also implement some sort of NSOperationQueue with a few lines of code based solely on GCD, say a "TaskQueue" and with basically the same features, like "maxConcurrentTasks" and can use it to enqueue task functions (not operations), in just a more powerful, more concise and a more comprehensible way as well. ;)

In order to get a cancelable operation, you need to create a subclass of NSOperation - while you can create a async function "ad-hod" - inline.

Also, since cancellation is an independent concept, we can assume, that there exists some library whose implementation is solely based on GCD which solves this problem in the, uhm, the usual way ;) It may look like this:

self.cancellationRequest = CancellationRequest()
self.asyncTask(param: param, cancellationToken: cr.token).map { result in
   ...
}

and later:

override func viewWillDisappear(_ animated: animated) {
    super.viewWillDisappear(animated)
    self.cancellationRequest.cancel() 
}

So, IMHO there's really no reason to use clunky NSOperation and NSOperationQueue, and there's no reason any more for subclassing NSOperation, which is quite elaborate and surprising difficult, unless you don't care about data races.

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67