There are at least two quite large asynchronous APIs that exist in other language but are not yet in Swift. These are async/await and actors. I’ve detailed both of these in the previous two parts of this mini-series.
Swift has GDC, and in Swift 3 the API for GDC became much cleaner and easier to use, meaning you can easily dispatch some code to be run asynchronously by just doing
let queue = DispatchQueue(label: “”, attributes: concurrent)
queue.async {}
You can use GCD and closures in Swift to create asynchronous functions that give you their result of their work in the closure. So we can define a few functions that perform an asynchronous task:
func getWebResource(completion: ((String) -> Void))
func getImage(completion: ((UIImage) -> Void))
func getTheLastThing(completion: ((String) -> Void))
Now when we call each of these our code looks like this:
func getEverything() {
getWebResource() { result in
getImage() { image in
getTheLastThing() { finalThing in
}
}
}
}
The problem with this is callback hell. The pyramid of doom. This can be solved and the code can be much cleaner and read sequentially by using async/await. This has already been implemented in Kotlin, and you can go back to the first part in this series to look at how it’s done there if you’re only an iOS developer. But essentially, by using async and await, you can write code in a much more clean and understandable way that’s still asynchronous.
The proposal from Chris Lattner which he has started to implement, means that functions will be marked as async, in the same way that you can mark a function with throws. So function signatures would look like:
func getWebResource() async -> String
func getImage() async -> UIImage
func getTheLastThing() async -> String
and then call it like:
func getEverything() {
let result = await getWebResource()
let image = await getImage()
let finalThing = await getTheLastThing()
}
All of this really is just syntactic sugar for completion handlers. It doesn’t change dispatch and it doesn’t pre-assume a concurrency model under the hood. This is the reason why it could potentially be in Swift 5, since it’s quite a small change (relative to other proposals).
Another subtle but important detail about this approach is that with the traditional closure method, your result might be a tuple of the data and an error, both being optional, and then you need to check if the error is not nil before using the result. This would then need to be detailed through documentation rather than just the way the API is used. With async/await, you can add both the async and throws function modifiers, so rather than your function returning a tuple, it can be marked as throws and just return the data, and throw if there’s an error. This way, handling an error of the async function is forced through the API rather than being in documentation and best practices.
You can see this code is much cleaner, reads sequentially, and removes the pyramids of doom. A pull request has been opened and this is the way in which this idea has been implemented, although more work still needs to be done before the request is ready. You can see it here: https://github.com/apple/swift/pull/11501
So that’s the first idea. It’s possible that this makes its way into Swift 5, though ABI stability may take priority over any new language features.
The second idea is Actors. The proposal of Actors in Swift is especially exciting as server-side libraries get more and more advanced. Each instance of an Actor is intrinsically tied to a new dispatch queue, which for server side apps, means when a new request comes in, a new Actor could be spun up and the work done on that Actor, which would be on its own queue. Actors in this proposal work in the same way that I talked through in the previous part, except with just a few differences.
The first difference is that Actors in the proposal can actually reply to messages. Because messages to an Actor are asynchronous, the actor function would need to intrinsically respond asynchronously. Actors can also call async tasks and await their results because they are intrinsically async themselves.
On an Actor, you can easily cause deadlock by sending yourself a message. The proposal suggests that this could be trivially diagnosed by the compiler, but more complex deadlocks would ideally be diagnosed at runtime with a trap.
Actors are at least a couple years away, but the concept is still interesting and it’ll be interesting to see how it evolves.