Episode #511

Adapting existing asynchronous APIs with Continuations

Series: Async / Await

9 minutes
Published on November 10, 2021

This video is only available to subscribers. Get access to this video and 573 others.

With Continuations we can bridge the non-async world and make it async. Continuations allow us to take the result of a completion block and turn it into an async flow. In this episode we will see the difference between checked and unchecked continuations as well as their throwing variants.

Let's see how we can use this do adapt to a completion block based API.

Note that I'm purposely ignoring the fact that image has an async version of this function ;)

We can use a checked continuation to allow the runtime to enforce that is called at most one time. This is usually the case, but not always. Having the type present this information to callers is extremely useful.

    func downloadThumbnail(_ url: URL) async throws -> UIImage {
        let session = URLSession.shared

        await Task.sleep(NSEC_PER_SEC)

        let (data, response) = try await session.data(from: url, delegate: nil)

        guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
            throw UnexpectedResponse()
        }

        guard let image = UIImage(data: data) else {
            throw InvalidImage()
        }

        let thumbSize = CGSize(width: 600, height: 600)
        let thumbRect = AVMakeRect(aspectRatio: image.size, insideRect: CGRect(origin: .zero, size: thumbSize))

        return try await withCheckedThrowingContinuation { continuation in
            image.prepareThumbnail(of: thumbRect.size) { image in
                guard let image = image else {
                    continuation.resume(throwing: InvalidImage())
                    return
                }
                continuation.resume(returning: image)
            }
        }
    }

This episode uses Xcode 13.0, Swift 5.5.