HomeiOS DevelopmentWrapping current asynchronous code in async/await in Swift – Donny Wals

Wrapping current asynchronous code in async/await in Swift – Donny Wals


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:

  1. You possibly can solely resume a continuation as soon as
  2. 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 be await-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.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments