Sunday, October 15, 2023
HomeiOS DevelopmentWrapping present asynchronous code in async/await in Swift – Donny Wals

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


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:

  1. You’ll be able to solely resume a continuation as soon as
  2. 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 be await-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.



Supply hyperlink

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments