Printed on: April 24, 2022
Swift’s async/await characteristic is an incredible approach to enhance the readability of asynchronous code on iOS 13 and newer. For brand new tasks, because of this 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 have to make fairly vital modifications to our codebase if it’s asynchronous API is at the moment based mostly on features 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 carry it into the async/await world. On this publish, I’ll discover one of many choices we now have to transform current, callback based mostly, asynchronous code into features which might be marked with async
and work with async/await.
Changing a callback based mostly perform to async/await
Callback based mostly features can are available in varied shapes and varieties. Nevertheless, most of them will look considerably like the next instance:
func validToken(_ completion: @escaping (End result<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 seem like. The purpose is that the perform takes a completion closure, and it has a few spots the place this completion closure known as with the results of our try and receive a legitimate token.
💡 Tip: if you wish to study extra about what
@escaping
is and does, check out this publish.
The simplest strategy to make our validToken
perform accessible as an async
perform, is to put in writing a second model of it that’s marked async throws
and returns Token
. Right here’s what the tactic signature seems to be like:
func validToken() async throws -> Token {
// ...
}
This technique signature seems to be loads cleaner than we had earlier than, however that’s fully anticipated. The tough half now’s to someway leverage our current callback based mostly perform, and use the End result<Token, Error>
that’s handed to the completion as a foundation for what we wish 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 possibly can see, we now have checked and unsafe continuations. To study extra in regards to the variations between these two completely 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 anticipate. If the perform you’re changing to async/await can fail, use a throwing continuation. If the perform will at all times name your callback with a hit worth, use an everyday continuation.
Earlier than I clarify extra, right here’s how the completed validToken
seems to be when utilizing a checked continuation:
func validToken() async throws -> Token {
return strive await withCheckedThrowingContinuation { continuation in
validToken { end in
swap consequence {
case .success(let token):
continuation.resume(returning: token)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
Once more, if you wish to study extra in regards to 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 End result<Token, Error>
, Swift is aware of that the success case of the consequence
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
features that can droop till the resume
perform on the continuation
object known as. This continuation object is created by the with*Continuation
perform, 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 because the continuation by no means produces a consequence.
The closure that you simply go to the with*Continuation
perform is executed instantly which implies that the callback based mostly model of validToken
known as immediately. As soon as we name resume
, the caller of our async validToken
perform will instantly be moved out of the suspended state it was in, and it is going to be in a position to resume execution.
As a result of my End result
can include an Error
, I additionally have to test for the failure case and name resume(throwing:)
if I would like the async validToken
perform to throw an error.
The code above is fairly verbose, and the Swift staff acknowledged that the sample above could be a fairly widespread one so that they supplied a 3rd model of resume
that accepts a End result
object. Right here’s how we are able to use that:
func validToken() async throws -> Token {
return strive await withCheckedThrowingContinuation { continuation in
validToken { end in
continuation.resume(with: consequence)
}
}
}
A lot cleaner.
There are two essential issues to bear in mind while you’re working with continuations:
- You possibly can solely resume a continuation as soon as
- You’re chargeable for calling
resume
in your continuation from inside your continuation closure. Not doing it will trigger the caller of your perform to beawait
-ing a consequence perpetually.
It’s additionally good to appreciate that every one 4 completely different with*Continuation
features make use of the identical guidelines, excluding whether or not they can throw an error or not. Different guidelines are fully similar between
Abstract
On this publish, you noticed how one can take a perform that takes a callback, and convert it to an async
perform by wrapping it in a continuation. You realized that there are completely different sorts of continuations, and the way they can be utilized.
Continuations are an superior strategy to bridge your current 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.
General, I feel continuations present a extremely easy and stylish API for changing current callback based mostly features into async/await.