Weekly Swift articles, podcasts and tips by John Sundell.

Networking

Published on 14 Jan 2019

Most apps these days need a way to download data from the Internet — whether that’s files, images, or by talking to some form of web API through a format like JSON. While there are a multitude of solutions and frameworks available to us when it comes to implementing networking in Swift — let’s take a look at how to use Foundation’s URLSession API to load data over the network.

It all starts with a URL. While the Swift standard library’s URL type allows us to create a URL value using a string, a more robust option is usually to use URLComponents. That’ll let us construct the various parts of a URL in steps, and then use the components’ url property to build the URL we need:

// Construct a URL by assigning its parts to a URLComponents value
var components = URLComponents()
components.scheme = "https"
components.host = "api.github.com"
components.path = "/users/johnsundell"

// This will give us the constructed URL as an optional
let url = components.url

Above we take the URL to use when requesting my GitHub profile data through their web API, and break it down into its scheme, host (the base URL of the server), and the path we’re interested in — and assign those values to an instance of URLComponents to have it construct our URL.

Next, let’s use our newly constructed URL to perform a network request. To do that we’ll use our app’s default URLSession to create a data task, and supply a completion closure that we wish to be called once the data task will be completed:

guard let url = components.url else {
    preconditionFailure("Failed to construct URL")
}

let task = URLSession.shared.dataTask(with: url) {
    data, response, error in
}

As you can see above, URLSession will send three arguments to our data task’s completion closure once finished:

Let’s say that we want to either take the data that we downloaded and render it as a string to a UILabel, or — in the case of an error — use the same label to show a description of the error that was encountered. To do that we’ll first unwrap the data optional, and if that fails we’ll instead use the localizedDescription of the passed error — like this:

if let data = data {
    label.text = String(decoding: data, as: UTF8.self)
} else {
    label.text = error?.localizedDescription
}

However, since our data task will execute asynchronously on a background thread (we wouldn’t want our entire app to pause while we’re waiting for a network request to finish) — we need to use Grand Central Dispatch to dispatch our UI update onto the main queue, since UIKit cannot be used from a background queue:

DispatchQueue.main.async {
    if let data = data {
        label.text = String(decoding: data, as: UTF8.self)
    } else {
        label.text = error?.localizedDescription
    }
}

Finally, to start our data task and actually perform our network request, we need to call resume() on it:

task.resume()

And that’s it! 🎉 Here’s a complete example, which when run will either display a JSON representation of my GitHub profile, or a description of any error that was encountered, using the label we created:

var components = URLComponents()
components.scheme = "https"
components.host = "api.github.com"
components.path = "/users/johnsundell"

guard let url = components.url else {
    preconditionFailure("Failed to construct URL")
}

let label = UILabel()

let task = URLSession.shared.dataTask(with: url) {
    data, response, error in

    DispatchQueue.main.async {
        if let data = data {
            label.text = String(decoding: data, as: UTF8.self)
        } else {
            label.text = error?.localizedDescription
        }
    }
}

task.resume()

While implementing a complete networking solution might involve several other tasks — such as posting data using the POST method (which can be done using the same technique as above, but by using a URLRequest instead of a plain URL), customizing caching behavior, and much more — using URLSession like we did above can be a great way to start.

Thanks for reading! 🚀