In Part 1, we looked at the basics of the NSOperation class and how to build our own. Today, we’ll taking things to the next level: we’ll look at how operations can work together in order to create new, complex behaviors.

Trust me: once you get familiar with this, you’ll see the uses everywhere.

This article is part of a 4-part series on Building robust, reliable code with NSOperations.

  1. Getting Started With NSOperation and NSOperationQueue
  2. Chaining a Series of NSOperations
  3. Grouping Common Operations Together
  4. Handling UI with NSOperations

Let’s have a look at the classic example of downloading and parsing data. We’ll start with 2 operations: one that downloads Data from a URL, and another that parses Data into a given model object. We want to achieve something like this:

As things stand, we have 2 challenges we need to overcome.

  • First of all, we need to make sure that the DownloadOperation executes before the ParseOperation. This is obvious, but not quite straightforward. These operations are asynchronous, running on background threads. How can we guarantee that they’ll execute in the correct order?
  • Secondly, we need to pass the downloaded Data from our DownloadOperation to our ParseOperation. Nothing exists to do this out of the box, so we’ll need to roll our own solution.

Let’s get started!

Dependencies — How to precisely control when your operations will execute

In the last article, we glossed over the Ready state of the NSOperation, but now it’s time to go more in depth. When an operation is Ready, it means that all of its dependencies have completed.

But wait a sec Frank, what do you mean by dependencies?

If operation B depends on operation A, the system guarantees that operation B will never execute before operation A. Regardless of the fact that operations are asynchronous, we can still reason about their timing and execution.

Dependencies also work between different operation queues and threads. Thanks Apple! 🙌🏻

What’s more, creating a dependency between two operations is extremely easy. It’s as simple as this:

operationB.addDependency(operationA)

So this is how we get over our first hurdle: if we make our ParseOperation depend on our DownloadOperation, we no longer longer need to worry about them executing out of order. Great!

Adapters - How to pass data from one operation to another

At the moment we have 2 separate operations. I rather not combine them, since they each make sense on their own. A DownloadOperation could download data that isn’t parsed (like a video), and a ParseOperation could be used to parse data that isn’t downloaded (like a file on disk).

But now we need to get these operations to talk to each other. There are a few ways we could go about this, but my favourite is using a third operation: the adapter operation.

An adapter operation is simply a BlockOperation that takes data from one operation and passes it to the other. These are single-use operations that we can build on the fly when we need them.

It’ll be sandwiched between our 2 operations. That means we need to change how our dependencies are handled. Instead of having our ParseOperation depend on our DownloadOperation, we’ll have it depend on our adapter operation. Also, our adapter operation will depend on our DownloadOperation. It’ll look something like this:

This effectively creates a chain of operations that you know will execute in the right order. Here’s what the code would look like:

let downloadOp = DownloadOperation()
let parseOp = ParseOperation()
let adapterOp = BlockOperation() {
    parseOp.data = downloadOp.data
}

parseOp.addDependency(adapterOp)
adapterOp.addDependency(downloadOp)

queue.addOperations([downloadOp, adapterOp, parseOp])

So what’s going on here exactly? When our DownloadOperation finishes, the adapter operation will have all of its dependencies satisfied. Then it’ll execute, taking the data from the DownloadOperation and setting it on the ParseOperation. Once that’s done, the adapter operation will finish and the ParseOperation will have its dependencies satisfied. It’ll execute and parse the data that was passed to it.

Pretty cool, n’est-ce pas?

Watch out for retain cycles and deadlocks

There’s one last thing we need to add to our example above, because as things stand, we have a nasty memory leak going on. To avoid this, we need to add a capture list to our adapter operation to weakly capture the references to the other operations. If we don’t, none of our operations will ever deallocate.

let adapterOp = BlockOperation() {
    [unowned downloadOp, unowned parseOp] in
    parseOp.data = downloadOp.data
}

I’d like to extend my thanks to Nick Harris for figuring this out! You can find his article about it here: Retain Cycle with NSOperation Dependencies (His blog has plenty of amazing resources on NSOperations in Swift. I highly recommend poking around and reading it!)

Also, a quick word of warning. If you make 2 operations dependant on each other (i.e. operation A depends on B, and B depends on A), neither of them will execute. This is a deadlock, where each operation is waiting for the other to finish.

Feel the power!

There you have it! You’re now able to create Operations and chain them together.

Though as you can imagine, you’ll often be downloading and parsing together. Next time, we’ll look at how we can wrap commonly used operations together so we can keep our code nice and DRY.

Disqus Recommendations

We were unable to load Disqus Recommendations. If you are a moderator please see our troubleshooting guide.

Disqus Comments

We were unable to load Disqus. If you are a moderator please see our troubleshooting guide.

G

Start the discussion…

Comment

Log in with
or sign up with Disqus or pick a name

Disqus is a discussion network

  • Don't be a jerk or do anything illegal. Everything is easier that way.

Read full terms and conditions

This comment platform is hosted by Disqus, Inc. I authorize Disqus and its affiliates to:

  • Use, sell, and share my information to enable me to use its comment services and for marketing purposes, including cross-context behavioral advertising, as described in our Terms of Service and Privacy Policy, including supplementing that information with other data about me, such as my browsing and location data.
  • Contact me or enable others to contact me by email with offers for goods or services
  • Process any sensitive personal information that I submit in a comment. See our Privacy Policy for more information

Acknowledge I am 18 or older

Favoriting means this is a discussion worth sharing. It gets shared to your followers' Disqus feeds, and gives the creator kudos!

Find More Discussions

Share

  • Tweet this discussion

    • Share this discussion on Facebook
    • Share this discussion via email
    • Copy link to discussion
  • Best

Be the first to comment.

Load more comments

live.rezync.com

live.rezync.com is blocked

This page has been blocked by an extension

  • Try disabling your extensions.

ERR_BLOCKED_BY_CLIENT

Reload

This page has been blocked by an extension

pippio.com

pippio.com is blocked

This page has been blocked by an extension

  • Try disabling your extensions.

ERR_BLOCKED_BY_CLIENT

Reload

This page has been blocked by an extension