Managing async code in Swift
Async programming may be tricky. If you are a beginner, things can break without any apparent reason, and code can become spaghetti-like. But you are not alone. With the appropriate tools, all those problems can be avoided.
First approach: Closures
The first thing that anyone does when beginning writing asynchronous code is using closures… everywhere…
Closures (or callbacks) are the easiest way to write async code. The approach consists in let your async function accept a last parameter that is actually another function. That function is called after the async code finishes, either with an error or a value.
For example, look at this gist:
The async function does what it’s supposed to do, and finally calls to the completion function argument. As it can fail, the asynchronous function should be able to pass an Error to the closure.
It is simple, and does what it has to do, but in more complex scenarios, this approach is not enough.
This is a pretty common scenario, and everyone (included me) has written code like that, or worse. But there is a solution.
Second approach: Futures
The second approach is using Futures (or Promises). A future is a value that symbolizes a value that can be returned at any time. The future is not the value, it’s a promise that the value will come.
Using futures works like this:
- You have a function that perform async code, but it returns a Future object before doing so.
- You call that function and subscribe to that future, so when that future is resolved, your subscription callback is called. The common name of the method that accepts a callback to execute after the future is resolved, is commonly called then.
- You can chain futures, so when the first future is resolved, the second async function starts using the first async function result. You can chain a third future, a fourth, and so on. This is also called flatMap.
- You can transform the result of a future to a different kind of value. This is also called map.
The best of using Futures is that you can easily compose functional pipelines.
Just check this:
In this example, I am using the Microfutures library. You can see that nothing is nested in more than a few levels.
Using futures is an excellent alternative, and continues being simple enough to learn it in no more than a few hours, turning this approach in a perfect one for beginners that are looking for a way to stop using the callbacks based approach.
Third approach: Functional Reactive Programming
FRP is the hot topic within the Swift community currently. It lets you compose data streams that can be chained, transformed, and merged in several ways.
FRP consists in three steps:
- Create a stream: Anything can be a stream, from an async API call to a button tap.
- Transform the stream: The stream can be transformed in several ways. You can transform it using map and flatMap (just like Futures), or merge it with other streams, for example.
- Subscribe to the stream: You subscribe to the stream, so every time the stream gets new data, your callback is called, and something is then performed.
Ok, so it is the same thing as Futures, right? No. It’s not. It’s much more powerful than Futures based approach, in a fundamental way. The stream can be updated at any moment, so your callback will be called every time the stream gets updated.
When that stream is updated, something can be performed, and you can use that to build UI that is a function of the state of the application, which means that your UI will change, reflecting any change in the state, making it declarative. You start describing your system, rather than using an imperative way to program it.
RxSwift (and its UI related library, RxCocoa) is an excellent implementation of this approach.
Okay, this is not available today in Swift 3, but rumors are that this can come in Swift 4 or 5. (http://researcher.watson.ibm.com/researcher/files/us-lmandel/lattner.pdf, page 52.)
The great benefit of this approach is that async code is written in the same way as sync code, which is a great, great, great advantage!
If you want to learn about Async/await in other languages, you can read this.
Everyone started managing async code using callbacks, producing spaghetti code and entering in the awful world of callback hell.
For beginners who want to exit from that model, I recommend using Futures. There are a lot of implementations of Futures in Swift, including great libraries like PromiseKit. (I also have an implementation of Futures called Microfutures, because is very lightweight and has only the necessary features.)
I hope you have enjoyed this brief introduction. And please leave your comments below.