In January we announced the launch of Bolts, an open source library of mobile developer tools from Parse + Facebook. We plan to open source a number of these tools in Bolts, but we started with Tasks, which make the organization of complex asynchronous code more manageable.

Asynchronous programming is not an easy concept to understand, so let’s start with an analogy. Imagine you’re cooking Thanksgiving dinner with your family. You’re making the turkey, your sister is making a pie, and your Dad is making a casserole. Now let’s imagine that in this universe, once you put something in the oven, you have to stare at it and wait for it to be finished cooking. Pretty soon, everyone will be standing around the oven staring at it, and no one will be doing anything useful. Someone needs to make the cranberry salad, but no one can do it right now, because you’re all too busy staring at the oven, waiting for your food to finish cooking.

Sounds crazy, right? But that’s exactly how most web servers are built today. Instead of people, there are threads. Threads are what actually do things in your app. Nothing gets done without having some thread to do it. And the oven can represent any kind of task that takes time but doesn’t require CPU. Usually, that means waiting on a network connection, maybe to a database server. So how do we free up threads to do more important work?

What if we got a faster oven, like a microwave? Then we’ll spend less time staring at the turkey, and we can get on with the cranberry salad. This means making the Internet faster, and network cards faster, and hard drives faster. This would be great, but there’s a practical limit to how much we can speed those things up in the short term.

Another option is to do what television chefs do. On a show, they’ll make cake batter, put it in the oven, and then immediately pull out a finished cake that started baking before the show. That’s basically what memcached does. If you know ahead of time what people are going to want, you can go ahead and prepare it ahead of time. Unfortunately, there are lots of times when you can’t predict what people are going to want.

Okay, so what if we just got more people to help cook? Mom can come in and make the cranberry salad while everyone else is staring at the oven. In theory this should work, but in practice, it doesn’t. It’s a bit like filling your kitchen with so many people that everyone’s crammed in and no one can get anything done. There’s only so many threads that modern operating systems can handle before scheduling and memory and stack space get overwhelmed. So we’re going have to rethink the way we design our code.

Well, how do we actually solve this problem in the kitchen? In real life, we don’t stand around staring at the oven. We have an oven timer!

Callbacks

With an oven timer, we can put the turkey in the oven, set it to go off later, and then go do other things. We can start preparing the cranberry salad. Then, when the turkey is finished, it will ding, and we know to go back to it. In code, that’s called a callback.


oven.cook(turkey, function() {
     console.log(“The turkey is cooked!”);
});

With a callback, you create a function with what you want done after the job is done, and then when you start the job, you give it the callback. When the job is done, it calls the callback, and you can continue from there.

But callbacks also have some downsides. The biggest is that they tend to make code messy. Because you have to pass a callback in when you start the long-running task, you kind of have to declare everything backwards. Instead of a straightforward list of commands, like in a recipe, you have your instructions scattered all over the place. It’s difficult to even guarantee that they get called exactly once.

And there’s another problem with callbacks that’s particular to iOS programming. Objective-C uses reference counting for memory management, and this can cause retain cycles.

Imagine the turkey has a reference to its cook method, and the cook method has a reference to the callback, and the callback has a reference to the turkey. Nothing can be garbage-collected. That’s a memory leak.

Okay, so hopefully I’ve convinced you that there are lots of problems that can creep into your code when you use callbacks. So what are the alternatives?

Reactive extensions

One technology that solves many of these problems is Reactive Extensions (Rx). Reactive Extensions is a set of tools originally developed by Microsoft that let you model asynchronous streams of data. If you want to handle events like a stream of turkeys coming out of your oven, or more likely, a stream of mouse clicks, you should use Rx. There are open source libraries for many major platforms, including Reactive Cocoa, RxJava, and RxJS. Going into detail on how Rx works would take far more time to explain than we have here.

There is one minor issue with Rx. It’s really designed around streams of data. If you’re making thanksgiving dinner, the stream could just happen to emit exactly one turkey. But there’s nothing about Reactive Extensions that enforces that. Sometimes we really want to structure our code to enforce that we have exactly one turkey.

Promises/Tasks

And that brings us to Tasks. A task represents the result of some process, which may be asynchronous. And it lets you add callbacks that get run whenever the task completes. You may have heard tasks referred to as Promises before. They’re pretty much the same thing. “Promises” are the name for this concept in JavaScript, where it’s currently very popular. But they’re also a big part of Microsoft’s current APIs. In .NET, they are part of a package called the Task Parallel Library. At Parse + Facebook, we love developing with tasks, and wanted to make them available to everyone. That’s why Tasks are the first component in Bolts.

Some of the advantages you’ll get from using tasks:

    1. You can chain tasks together, and you can add callbacks after tasks complete, so you can write your code in the order you want it to happen.
    2. Errors get propagated automatically. When you call “then” to add a callback to a task, it automatically wraps it in a try-catch block that converts any exception into a failed Task. So any future callbacks will still get called, and they can determine whether to propagate the error or handle it.
    3. With tasks, it’s much easier to guarantee that your callback code gets called exactly once.
    4. You’re much less likely to get retain cycles when reference counting. In Objective-C, it’s a significant benefit over using something like NSOperation. Remember that retain cycle from earlier that kept us from freeing memory? Let’s look at what that would be with tasks.

Instead of the cook function keeping track of its callbacks directly, it uses a task. After cooking is completed, the task is returned, and the method no longer needs to keep track of it. And, after it calls its callbacks, it forgets about them. So the diagram ends up like this:

There are no cycles now, and everything cleans up after itself. This is one of the reasons we like using Tasks in Objective-C so much. And with Bolts, we think you will too.

Leave a Reply

To help personalize content, tailor and measure ads, and provide a safer experience, we use cookies. By clicking or navigating the site, you agree to allow our collection of information on and off Facebook through cookies. Learn more, including about available controls: Cookies Policy