Revealed on: April 24, 2022
Swift’s async/await characteristic is an incredible manner to enhance the readability of asynchronous code on iOS 13 and newer. For brand spanking new initiatives, which means we are able to write extra expressive, extra readable, and simpler to debug asynchronous code that reads similar to synchronous code. Sadly, for a few of us adopting async/await implies that we’d must make fairly vital adjustments to our codebase if it’s asynchronous API is at the moment based mostly on capabilities with completion handlers.
Fortunately, we are able to leverage a few of Swift’s built-in mechanisms to supply a light-weight wrapper round conventional asynchronous code to deliver it into the async/await world. On this publish, I’ll discover one of many choices we now have to transform present, callback based mostly, asynchronous code into capabilities which are marked with async
and work with async/await.
Changing a callback based mostly operate to async/await
Callback based mostly capabilities can are available in varied shapes and types. Nevertheless, most of them will look considerably like the next instance:
func validToken(_ completion: @escaping (Outcome<Token, Error>) -> Void) {
let url = URL(string: "https://api.web.com/token")!
URLSession.shared.dataTask(with: url) { knowledge, response, error in
guard let knowledge = knowledge else {
completion(.failure(error!))
}
do {
let decoder = JSONDecoder()
let token = strive decoder.decode(Token.self, from: knowledge)
completion(.success(token))
} catch {
completion(.failure(error))
}
}
}
The instance above is a really simplified model of what a validToken(_:)
technique would possibly appear like. The purpose is that the operate takes a completion closure, and it has a few spots the place this completion closure is known as with the results of our try to get hold of a legitimate token.
💡 Tip: if you wish to study extra about what
@escaping
is and does, check out this publish.
The best approach to make our validToken
operate accessible as an async
operate, is to put in writing a second model of it that’s marked async throws
and returns Token
. Right here’s what the strategy signature appears to be like like:
func validToken() async throws -> Token {
// ...
}
This technique signature appears to be like lots cleaner than we had earlier than, however that’s fully anticipated. The tough half now’s to someway leverage our present callback based mostly operate, and use the Outcome<Token, Error>
that’s handed to the completion as a foundation for what we need to return from our new async validToken
.
To do that, we are able to leverage a mechanism referred to as continuations. There are a number of sorts of continuations accessible to us:
withCheckedThrowingContinuation
withCheckedContinuation
withUnsafeThrowingContinuation
withUnsafeContinuation
As you may see, we now have checked and unsafe continuations. To study extra concerning the variations between these two totally different sorts of continuations, check out this publish. You’ll additionally discover that we now have throwing and non-throwing variations of continuations. These are helpful for precisely the conditions you would possibly count on. If the operate you’re changing to async/await can fail, use a throwing continuation. If the operate will at all times name your callback with a hit worth, use a daily continuation.
Earlier than I clarify extra, right here’s how the completed validToken
appears to be like when utilizing a checked continuation:
func validToken() async throws -> Token {
return strive await withCheckedThrowingContinuation { continuation in
validToken { lead to
swap end result {
case .success(let token):
continuation.resume(returning: token)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
Once more, if you wish to study extra concerning the distinction between unsafe and checked continuations, check out this publish.
Discover how I can return strive await withChecked...
. The return sort for my continuation will likely be the kind of object that I go it within the resume(returning:)
technique name. As a result of the validToken
model that takes a callback calls my callback with a Outcome<Token, Error>
, Swift is aware of that the success case of the end result
argument is a Token
, therefore the return sort for withCheckedThrowingContinuation
is Token
as a result of that’s the kind of object handed to resume(returning:)
.
The withCheckedThrowingContinuation
and its counterparts are all async
capabilities that may droop till the resume
operate on the continuation
object is known as. This continuation object is created by the with*Continuation
operate, and it’s as much as you to utilize it to (ultimately) resume execution. Not doing it will trigger your technique to be suspended perpetually for the reason that continuation by no means produces a end result.
The closure that you just go to the with*Continuation
operate is executed instantly which implies that the callback based mostly model of validToken
is known as immediately. As soon as we name resume
, the caller of our async validToken
operate will instantly be moved out of the suspended state it was in, and it will likely be capable of resume execution.
As a result of my Outcome
can comprise an Error
, I additionally must test for the failure case and name resume(throwing:)
if I would like the async validToken
operate to throw an error.
The code above is fairly verbose, and the Swift group acknowledged that the sample above could be a fairly widespread one so that they supplied a 3rd model of resume
that accepts a Outcome
object. Right here’s how we are able to use that:
func validToken() async throws -> Token {
return strive await withCheckedThrowingContinuation { continuation in
validToken { lead to
continuation.resume(with: end result)
}
}
}
A lot cleaner.
There are two vital issues to remember while you’re working with continuations:
- You’ll be able to solely resume a continuation as soon as
- You might be liable for calling
resume
in your continuation from inside your continuation closure. Not doing it will trigger the caller of your operate to beawait
-ing a end result perpetually.
It’s additionally good to comprehend that each one 4 totally different with*Continuation
capabilities make use of the identical guidelines, except whether or not they can throw an error or not. Different guidelines are fully equivalent between
Abstract
On this publish, you noticed how one can take a operate that takes a callback, and convert it to an async
operate by wrapping it in a continuation. You realized that there are totally different sorts of continuations, and the way they can be utilized.
Continuations are an superior approach to bridge your present code into async/await with out rewriting your entire code without delay. I’ve personally leveraged continuations to slowly however certainly migrate massive parts of code into async/await one layer at a time. With the ability to write intermediate layers that assist async/await between, for instance, my view fashions and networking with out having to fully rewrite networking first is superior.
Total, I feel continuations present a very easy and chic API for changing present callback based mostly capabilities into async/await.