Dealing with async code in Swift

Asynchronous programming can be tricky, especially for less experienced developers. There are many things to consider like thread the code is executing on, priorities or deadlocks. There are also many tools and practices that make it easier to write asynchronous code.

Piotr Tobolski
8 min readMar 19, 2018

In this post I would like to compare different styles of writing asynchronous code in Swift. Most of it also apply to Objective-C and other languages (like Java or C#) because concepts between platforms are shared very often. Main difference would be syntax and libraries used.

If you want to skip the basics jump to Promises section.

Let’s assume we are in common scenario where we have to download some data from a service and process it. Seems like an easy task, right? One could just write it like this:

let downloadedData = try! Data(contentsOf: url)
let image = UIImage(data: downloadedData)!
let processedImage = process(image)
updateUI(processedImage)
// voilá!

But there are some issues with this code. Let’s fix them.

First: there are many places where it can fail. Force unwrapping contents value coming from network call doesn’t look like a good idea. We should add some error handling.

do {
let downloadedData = try Data(contentsOf: url)
if let image = UIImage(data: downloadedData) {
let processedImage = process(image)
updateUI(processedImage)
// voilá!
} else {
... // handle problem with downloaded data
}
} catch {
... // handle data download error
}

This is better. Unfortunately it doesn’t read as good as the previous one. Errors are handled in multiple places even though they are similar and what is worse: everything is happening on the same thread. This is definitely not what we want. Running it on main thread would block UI. Using background thread would cause problems with updating UI. But how exactly should we run this code on different thread?

Threads

Using other thread is the simplest (conceptually) solution. Let’s see how it will look like.

Thread.detachNewThread {
do {
let downloadedData = try Data(contentsOf: url)
if let image = UIImage(data: downloadedData) {
let processedImage = process(image)
self.performSelector(onMainThread: #selector(updateUI(_:)), with: processedImage, waitUntilDone: false)
// voilá?
} else {
... // handle problem with downloaded data
}
} catch {
... // handle data download error
}
}

Great! This code can go into production app, right? It won’t block UI and it will work correctly. Just hope that nobody will ever maintain it. And that it isn’t called frequently (e.g. in UITableViewCell). Operating system doesn't like dispatching many threads. It won't perform very good and in worst case scenario it could cause the app to crash. This code doesn't look as bad as I expected because of this relatively new API. Some time ago using NSThread would require creating at least dedicated selector for executing the asynchronous code.

Grand Central Dispatch

Ok, I know, every Swift developer knows GCD already. It is way more common than usage of NSThreads (and this is good). But it’s less popular to know how it operates, maybe because of not being taught at universities. It uses concept of queues. Queues are not threads and they have a lot of advantages. Of course there are still cases where threads are the way to go but if you are in this kind of situation you surely know this already. In my opinion the best thing about using dispatch queues is not increased performance but the quality of code produced and general ease of use.

If you have a lot of work to do you may want to use NSOperationQueue but I will not talk about it here. I also recommend reading Concurrency Programming Guide for more details about concurrency. But let’s look at the code.

DispatchQueue.global(qos: .userInitiated).async {
do {
let downloadedData = try Data(contentsOf: url)
if let image = UIImage(data: downloadedData) {
let processedImage = process(image)
DispatchQueue.main.async {
updateUI(processedImage)
// voilá!
}
} else {
... // handle problem with downloaded data
}
} catch {
... // handle data download error
}
}

We got rid of the ugly performSelector as we could just schedule a block on the main queue. In the code above we can also see usage of QoS because why not? It doesn't add single line of code and makes the app perform better. There are also no problems with running a lot of operations in parallel. Splitting the download and processing into different queues with different QoS would be also very easy but there are some problems. This code doesn't read well. There are 4 indentation levels. Splitting it into several functions also wouldn't make it easier to read because we would have to jump around the code even more.

Promises

Here we are. This is something new. What exactly are promises (also called futures)? Promise represents a future value. If an API returns a promise it is promising that in future there will be some value. This way we can avoid passing blocks as arguments to get result of an asynchronous operation. Instead, function that can’t return value immediately, can return a promise. Promise that some value will be returned later. Promises also incorporate another feature: better error handling. Things can go wrong and API may not be able to fulfill the promise. In this case promise can be rejected. In this example I have used framework called promises made by Google. It doesn’t really matter which promises implementation is used as all of them are lightweight and very similar. I recommend checking also PromiseKit.

Promise(url)
.then(on: .global(qos: .userInitiated)) {
return try Data(contentsOf: url)
}.then(on: .global(qos: .background)) { data in
guard let image = UIImage(data: data) else { throw imageError }
return image
}.then(on: .global(qos: .utility)) { image in
return process(image)
}.then { image in
updateUI(image)
}.catch {
... // handle errors
}

There is still some clutter but we got rid of the redundant indentation and we could easily add more queues to execute the work on. With plain GCD we would get pyramid of doom. It is easier to read and errors are handled in one place. Main problem is that we are using API that is not made with promises in mind. Creating such API is very easy so let’s try doing it. Also why wrapping single function calls in closures when we can pass the functions themselves?

func imageFrom(data: Data) throws -> UIImage {
guard let image = UIImage(data: data) else { throw imageError }
return image
}
Promise(url)
.then(on: .global(qos: .userInteractive), Data.init(contentsOf:))
.then(on: .global(qos: .background), imageFrom(data:))
.then(on: .global(qos: .utility), process(_:))
.then(updateUI(_:))
.catch(handleError(_:))

In this form the logic is written in a short and concise way without compromising performance and flexibility. Debugging is also straightforward as we can easily add then blocks for debugging. Synchronizing work is also easy as promise frameworks offer functions like all or any that allow composing promises. The concept of promises is mature enough to ship with languages (e.g. Java 8). It is sad that they aren't natively available in Swift yet. Can we improve it more?

Functional Reactive Programming

FRP is actually different programming paradigm. Its main concepts are data streams and propagation of the changes. But what it have in common with promises? Well, let’s look at the sample code using RxSwift.

_ = Single.just(url)
.observeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
.map(Data.init(contentsOf:))
.observeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.map(UIImage.init(data:))
.errorOnNil()
.observeOn(ConcurrentDispatchQueueScheduler(qos: .utility))
.map(process(_:))
.observeOn(ConcurrentMainScheduler.instance)
.subscribe(onSuccess: updateUI(_:), onError: handleError(_:))

It looks very similar to the code created by using Promises. In fact Observable of type Single is just like a Promise. It is a stream of data that can produce only one value that is equivalent to promise fulfillment. Errors are also handled in a similar way. But there are some differences.

The first one is this weird assignment at the beginning. It is required to silence the compiler warning because all subscriptions return Disposable. It is the object responsible for the subscription lifecycle. Do you remember NSAutoreleasePool? This concept is similar. Subscriptions can be added to dispose bags and when those are disposed, all added subscriptions are deallocated. The subscription is also disposed when it completes so we don't need to dispose it in this case. But we could add it and easily get ability to cancel the subscription and free captured resources.

The second difference is the distinction of operators: map, observeOn and subscribe. RxSwift distincts between operators that lazily manipulate streams and subscriptions that cause cold observables to start producing events. I will not get into details here but there is one big important difference. Series of events (in this case downloading) starts when subscription is made. Promise will try to be fulfilled as soon as it is created without waiting for anyone to observe it. In RxSwift there are also hot observables that produce events without subscription but they are usually used in different scenarios (e.g. UI interaction).

The third and last difference I am writing about is threading. It is possible to use Schedulers that wrap not only Dispatch Queues but also Operation Queues or basically anything you want. When scheduler is set with observeOn, it is applied to the rest of the stream.

RxSwift is a different tool for different purposes. It adds a lot of complexity to the project but when used correctly is is very powerful. Reactive programming is a big shift in paradigm, it defines how we should look at tasks and data and shouldn't be considered as a single purpose tool.

Async / Await

Coroutines and concurrency model known as “async & await” is something we don’t have in Swift (yet). There is already a draft of Swift Evolution proposal made by Chris Lattner (author of Swift and LLVM). I will base the sample code on this proposal as most likely this it how it’s going to look like that when this feature is implemented.

Support for coroutines is an extension of the language that allows functions to not only return value but also to be suspended. It means that some code can be executed asynchronously without the need for completion blocks. It will be easier to see in an example.

// assuming code is called from an function marked "async"
do {
await DispatchQueue.global(qos: .userInteractive).syncCoroutine()
let downloadedData = try Data(contentsOf: url)
await DispatchQueue.global(qos: .background).syncCoroutine()
guard let image = UIImage(data: downloadedData) else { throw imageError }
await DispatchQueue.global(qos: .utility).syncCoroutine()
let processedImage = process(image)
await DispatchQueue.main.syncCoroutine()
updateUI(processedImage)
} catch {
await DispatchQueue.main.syncCoroutine()
... //handle errors
}

This code looks clean and simple. There is no indentation except for the do/catch block that clearly separates program logic from error handling. We can see a lot of syncCoroutine() calls, those exist because we are calling synchronous code and we want to switch the queue that the code is executing on. Error handling is easier than in promises as we can clearly see which code can throw and the compiler will check if we handled the errors.

The above code also resembles Promises in many ways. We could say that async functions return only promise that they will provide a value in the future. In fact if you read the proposal, you will see that there is a draft of simple Future implementation and mention that in C# async functions always return a promise (aka Task in C#).

Conclusion

There are many ways of dealing with async code in Swift. Applications are becoming more complicated over time and it is important to deal with asynchronicity correctly. Bugs in asynchronous code are arguably the most difficult to track and fix. I recommend to start learning FRP if you haven’t done so already. RxSwift may not be the best way for everyone at the time of being as it requires a lot of time and effort to shift the paradigm (and UIKit wasn’t built with reactive in mind) but Promises are easy to use and understand so I recommend everyone to find favorite promise framework and starting using it.

--

--