[ad_1]
Revealed on: April 24, 2022
Swift’s async/await characteristic is a tremendous method 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 signifies that we would have to make fairly important modifications to our codebase if it’s asynchronous API is at the moment primarily based on features with completion handlers.
Fortunately, we are able to leverage a few of Swift’s built-in mechanisms to offer a light-weight wrapper round conventional asynchronous code to convey it into the async/await world. On this publish, I’ll discover one of many choices we’ve got to transform present, callback primarily based, asynchronous code into features which can be marked with async and work with async/await.
Changing a callback primarily based perform to async/await
Callback primarily based features can are available numerous shapes and kinds. Nonetheless, most of them will look considerably like the next instance:
func validToken(_ completion: @escaping (Consequence<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 to be. The purpose is that the perform takes a completion closure, and it has a few spots the place this completion closure is named with the results of our try to receive a sound token.
💡 Tip: if you wish to study extra about what
@escapingis and does, check out this publish.
The best technique to make our validToken perform out there 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 strategy signature appears like:
func validToken() async throws -> Token {
// ...
}
This technique signature appears rather a lot cleaner than we had earlier than, however that’s fully anticipated. The difficult half now could be to someway leverage our present callback primarily based perform, and use the Consequence<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 out there to us:
withCheckedThrowingContinuationwithCheckedContinuationwithUnsafeThrowingContinuationwithUnsafeContinuation
As you’ll be able to see, we’ve got 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’ve got 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 a daily continuation.
Earlier than I clarify extra, right here’s how the completed validToken appears when utilizing a checked continuation:
func validToken() async throws -> Token {
return strive await withCheckedThrowingContinuation { continuation in
validToken { end in
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 kind for my continuation will probably 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 Consequence<Token, Error>, Swift is aware of that the success case of the end result argument is a Token, therefore the return kind 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 is named. 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 this may trigger your technique to be suspended without end for the reason that continuation by no means produces a end result.
The closure that you simply go to the with*Continuation perform is executed instantly which signifies that the callback primarily based model of validToken is named straight away. 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 will probably be capable of resume execution.
As a result of my Consequence can include an Error, I additionally have to examine for the failure case and name resume(throwing:) if I need the async validToken perform to throw an error.
The code above is fairly verbose, and the Swift workforce acknowledged that the sample above is likely to be a fairly widespread one so that they offered a 3rd model of resume that accepts a Consequence 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: end result)
}
}
}
A lot cleaner.
There are two essential issues to remember while you’re working with continuations:
- You may solely resume a continuation as soon as
- You’re liable for calling
resumein your continuation from inside your continuation closure. Not doing this may trigger the caller of your perform to beawait-ing a end result without end.
It’s additionally good to appreciate that each one 4 totally different with*Continuation features make use of the identical guidelines, apart from 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 discovered that there are totally different sorts of continuations, and the way they can be utilized.
Continuations are an superior technique to bridge your present code into async/await with out rewriting your whole code directly. I’ve personally leveraged continuations to slowly however certainly migrate massive parts of code into async/await one layer at a time. Having the ability to write intermediate layers that help async/await between, for instance, my view fashions and networking with out having to fully rewrite networking first is superior.
General, I believe continuations present a very easy and chic API for changing present callback primarily based features into async/await.
[ad_2]
