The Futures Are Now — iOS and Asynchronous Development
In programming, we often have work with asynchronous operations. This may include things such as network, file system, database, UI, long running tasks, or any other I/O events. In iOS, we have many mechanisms for handling this including GCD, NSOperation, NSNotifications, delegates, and callbacks.
But what exactly is a future? A future is a representation for a value that isn’t available yet. When you work with the future, you won’t know if the value is currently available. However, you can work with the value as if it were available. As soon as the value does become available, the future will process your instructions.
Frameworks for Futures
If you want to start working with futures in your iOS app, you have a couple of different third-party libraries to choose from, including:
Depending on which library you chose, you’ll have different design choices to work with. For example, Big Nerd Ranch’s Deferred library was inspired by the Deferreds from OCaml. This means that the future/deferred values do not say if the asynchronous operation has failed or not. To represent a failure, you’ll need to represent that as part of the value by returning a Result type or a similar associated enum. BrightFutures follow Scala’s promises and futures, so there is an implicit understanding that each future can potentially fail. FutureKit and Bolts also contain a “Cancelled” state, which represents when a user cancelled the future/task and no longer cares about the result.
These libraries may have different methods and design goals; however, they all solve the same issue of providing one interface to represent and work with asynchronous results. They also all address common tasks such as sequencing futures together, running futures in parallel, and handling the results on a specific thread.
So let’s dig in.
Just The Basics
The most basic way to work with futures is to set a completion handler. This simply allows you to perform an action once the value is available. To set the handler, you set your continuation function on the future by calling either onSuccess (BrightFutures/FutureKit), upon (Deferred), then (PromiseKit), or continueOnSuccessWith (Bolts). Most of these frameworks also allow you to attach an error handler by calling onFailure (BrightFutures), onError (FutureKit), or catch (PromiseKit).
This should feel similar to only using callbacks to handle asynchronous tasks. The main difference here is you will still have a userFuture object afterwards which you can perform further processing on, such as by adding more completion handlers or passing into another function or component.
Mapping With Futures
When working with futures, we can pass the future around without needing to handle the value with a callback. Suppose we have a future of one type (such as Future<JSON>), but we need a future of another type (such as Future<User>). If we have a function that can convert the first inner type (JSON) to the second inner type (User), we can call map on the Future<JSON> and pass in the mapping function to get a Future<User>.
When dealing with multiple futures, sometimes you will need the result of one future to get another future. For instance, if you need to look up a user before getting their address, you may need to connect the two requests like so:
While this is valid, notice how your main logic will be nested within two completion blocks. Also notice how if the call for getUser fails, we won’t be able to run getAddress. This action of chaining or sequencing futures is so common, there is a function on futures you can call to do this. The function is either named flatMap (BrightFutures/Deferred), then (PromiseKit), continueWithTask (Bolts), or onSuccess (FutureKit).
Futures in Parallel
Another common scenario is when you have multiple futures that are not dependent on one another, and you need all of the values before continuing. Most future frameworks will have a function to convert an array of futures into a single future that will hold an array of values. If all of the futures are successful, the new future will return with an array containing the values from all the futures. This function is called either sequence (BrightFutures), Task.whenAll (Bolts), joinedValues (Deferred) or when (PromiseKit).
Multiple Asynchronous Values
While futures are great, they are not appropriate for every kind of asynchronous processing. For example, if you want to build a chat app, you’ll need to handle a potentially infinite number of messages from other users. In this case, a reactive structure (such as Signal, Observable, or Stream) is needed. These are similar to futures, except they provide a sequence of values instead of just one value. Futures can actually be considered as a special kind of Signal, one that returns only one value. For more information on this, check out the RxSwift or ReactiveCocoa frameworks.
retrieveAge will actually return a Future<Int?>, and retrieveUserFromDB will return a Future<User?>. By using the await keyword, the rest of the code becomes asynchronous and will wait until retrieveAge returns, effectively putting the rest of the code in an onSuccess handler such as follows:
Notice how using await hides the fact that futures are being used and makes the code look like synchronous imperative programming, which should be familiar to many.
As of Swift 3, this feature does not exist for Swift yet. However, according to this message on the swift-evolution mailing list, Chris Lattner said this is a possible Swift 4 Stage 2 effort. So hopefully we will see this added eventually.
Futures are a great construct to use if you need to deal with asynchronous results. They have many benefits over simple callbacks since you can pass them around and compose them together in sequence or in parallel. For now, you’ll have to use a third party framework to get this functionality, but learn about futures now so you’ll have that tool in your programming arsenal. That way, you’ll be prepared if futures are ever fully integrated into the Swift programming language.