Daily coverage of WWDC21.
A Swift by Sundell spin-off.

Using URLSession’s async/await-powered APIs

Published at 11:35 GMT, 08 Jun 2021
Written by: John Sundell

One of the major new features that are being introduced in Swift 5.5 is async/await, and as part of their latest SDKs, Apple have also updated many of their built-in asynchronous APIs to take advantage of this new feature.

In this article, let’s take a look at URLSession specifically, and how we can now use the async/await pattern to perform tasks such as data fetches, downloads and uploads.

Loading some form of Data using a standard GET request is arguably the most common type of network call that apps perform on a regular basis, and using async/await, this can now be done using just a single line of code:

let (data, response) = try await URLSession.shared.data(from: url)

Apart from the new await keyword, the above call uses the standard try keyword to indicate that an error might be thrown, and then stores the result using a deconstructed tuple that’s made up of two parts — the data that was downloaded, and the response that was received.

Taking things a bit further, here’s how we could use the above new API to first load a set of JSON data from a given URL, and then decode that data using Swift’s built-in Codable API:

struct ItemLoader {
    var session = URLSession.shared

    func loadItems(from url: URL) async throws -> [Item] {
        let (data, _) = try await session.data(from: url)
        let decoder = JSONDecoder()
        return try decoder.decode([Item].self, from: data)
    }
}

Note how we’re currently using an underscore to ignore the response value that was returned from our asynchronous call, which is something that can be done in contexts where we don’t wish to handle the response itself in any way.

Since we’re calling an asynchronous method within loadItems, that method itself also needs to be marked with the async keyword, and we can once again use try (this time in combination with marking our method with the throws keyword) to propagate any error that was encountered.

Downloading files

Next, let’s take a look at how we can also use the async/await pattern to perform file downloads. The difference between such requests and the data API that we just took a look at above is that download tasks write their results to disk, rather than storing them as Data values in memory, which is especially useful for larger pieces of data, such as files.

So when calling the download API (which can once again be done using the new await keyword), we get back a localURL at which the downloaded file was stored, rather than a Data representation of its contents:

struct FileLoader {
    var session = URLSession.shared

    func downloadFile(from url: URL) async throws -> File {
        let (localURL, _) = try await session.download(from: url)
        return File(url: localURL)
    }
}

We could of course have returned our localURL value as-is, but in this case, we’re wrapping it within a simple File struct to clearly indicate that we’re actually returning a reference to a file.

Uploading files

To perform an upload, we can’t simply use a plain URL as our destination, but must instead pass a URLRequest instance that contains additional metadata, for example what HTTP method that we wish to use.

In this example we’re using a POST request to upload a piece of Data to a given URL, and we’re then returning the URLResponse that was received:

struct DataUploader {
    var session = URLSession.shared

    func upload(_ data: Data, to url: URL) async throws -> URLResponse {
        var request = URLRequest(url: url)
        request.httpMethod = "POST"

        let (responseData, response) = try await session.upload(
            for: request,
            from: data
        )

        return response
    }
}

Note how we probably should’ve ignored the responseData value using the same underscore technique that we used earlier (given that we’re not actually using that value), but in this example I thought it was important to include it for additional clarity.

Conclusion

So that’s three different ways to use async/await with URLSession. In upcoming articles, we’ll explore this new pattern further, and we’ll also take a look at how we can connect async calls to things like SwiftUI views.

For more information about URLSession and these new APIs, make sure to check out Apple’s official documentation.

Thanks for reading!

Written by: John Sundell
RevenueCatRevenueCat

Easily build and manage iOS and Android in-app purchases. With just a few lines of code RevenueCat provides IAP infrastructure, customer analytics, data integrations, and gives you time back from dealing with edge cases and updates across all platforms.