Friday, August 12, 2022
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 a tremendous manner to enhance the readability of asynchronous code on iOS 13 and newer. For brand spanking new initiatives, because of this we will 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 would must make fairly vital adjustments to our codebase if it’s asynchronous API is at present based mostly on capabilities with completion handlers.

Fortunately, we will 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 put up, I’ll discover one of many choices we have now to transform present, callback based mostly, asynchronous code into capabilities which can be marked with async and work with async/await.

Changing a callback based mostly perform to async/await

Callback based mostly capabilities can are available varied shapes and kinds. Nonetheless, 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 = attempt 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 is known as with the results of our try and acquire a sound token.

💡 Tip: if you wish to study extra about what @escaping is and does, check out this put up.

The best strategy to make our validToken perform accessible as an async perform, is to write down a second model of it that’s marked async throws and returns Token. Right here’s what the strategy signature seems like:

func validToken() async throws -> Token {
    // ...
}

This technique signature seems so much cleaner than we had earlier than, however that’s fully anticipated. The difficult half now could be to by some means leverage our present 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 will 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 have now checked and unsafe continuations. To study extra in regards to the variations between these two completely different sorts of continuations, check out this put up. You’ll additionally discover that we have now 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 successful worth, use a daily continuation.

Earlier than I clarify extra, right here’s how the completed validToken seems when utilizing a checked continuation:

func validToken() async throws -> Token {
    return attempt await withCheckedThrowingContinuation { continuation in
        validToken { lead to
            change 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 put up.

Discover how I can return attempt await withChecked.... The return sort for my continuation will likely be the kind of object that I move 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 capabilities that can droop till the resume perform on the continuation object is 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 without end because the continuation by no means produces a consequence.

The closure that you simply move to the with*Continuation perform is executed instantly which implies that the callback based mostly model of validToken is known as instantly. 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 will likely be in a position to resume execution.

As a result of my End result can comprise an Error, I additionally must verify 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 could be a fairly frequent one in order that they supplied a 3rd model of resume that accepts a End result object. Right here’s how we will use that:

func validToken() async throws -> Token {
    return attempt await withCheckedThrowingContinuation { continuation in
        validToken { lead to
            continuation.resume(with: consequence)
        }
    }
}

A lot cleaner.

There are two essential issues to bear in mind once 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 perform to be await-ing a consequence without end.

It’s additionally good to appreciate that every one 4 completely different with*Continuation capabilities make use of the identical guidelines, except whether or not they can throw an error or not. Different guidelines are utterly similar between

Abstract

On this put up, 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 present code into async/await with out rewriting your whole code directly. I’ve personally leveraged continuations to slowly however absolutely migrate giant parts of code into async/await one layer at a time. With the ability to write intermediate layers that help async/await between, for instance, my view fashions and networking with out having to utterly rewrite networking first is superior.

General, I feel continuations present a extremely easy and stylish API for changing present callback based mostly capabilities into async/await.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular