Skip to main content

Command Palette

Search for a command to run...

Using Swift’s Result type

Updated
3 min read
Using Swift’s Result type
C

Passionate about: Defensive Programming • Effective Engineering Documentation • Emotion-Centered Design and Microcopy

In the last post, I wrote about using Codables to simplify the code for deserializing JSON. Another neat networking feature of Swift that came out in the past couple years with Swift 5 is the Result type. While Codable assists with deserializing the response data, the Result type comes in handy a bit later in the networking flow—in the networking method’s completion handler. The Result type improves the readability of networking code by providing a sort of template in which you explicitly define the objects you’re getting back from the network call.

I’ll show an example using this Numbers API.

First, we’ll define our error types. Having typed errors makes it easier to handle errors with more granularity, i.e. respond differently to each error type.

enum NetworkError: Error {
    case badURL, requestFailed, unknown
}

Next, we’ll write the method for making the network request. Here is where the Result type comes in. Under the hood, Result is an enum with 2 cases: success and failure. This simply means that we’ll pass one of two things to our completion handler: .success or .failure. This is easier to read and understand at a glance compared to the traditional model where success or failure is determined by the arguments passed in for data, response, and error.

// Result will either be a String on success or NetworkError on failure. The first value of the generic is always the Success case, and the second is always the Failure case.
func fetchData(from urlString: String, completion: @escaping (Result<String, NetworkError>) -> Void) {
    // Check the URL is OK, otherwise return with a failure
    guard let url = URL(string: urlString) else {
        completion(.failure(.badURL)) // Pass in a failure case, specifying the type of error
        return
    }

    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        if error != nil {
            // Any sort of network failure
            completion(.failure(.requestFailed))
            return
        } else if let data = data {
            // Successful response: convert the data to a string and send it back
            let stringData = String(decoding: data, as: UTF8.self)
            completion(.success(stringData))
        }
    }
    task.resume()
}

And finally, we’ll call the method we just wrote above:

fetchData(from: "http://numbersapi.com/42") { result in
    switch result {
    case .success(let stringData): // The response data string we passed in (along with the success case) to the completion handler
        print(stringData)
    case .failure(let error):
        switch error {
        case .badURL:
            print("Bad URL")
        case .requestFailed:
            print("Network problems")
        }
    }
}

If you’re not familiar with syntax like .success(stringData) and case .success(let stringData), you can read more about enum associated values here: https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html#ID148. The Result type’s success and failure cases are both defined to take arguments, you’re required to pass something in (Data, Int, UIImage, String, Error, etc) to the Result case wherever you’re using it.

More from this blog

Y

Yours Swiftly 📱✨

10 posts

iOS Developer based in the Bay Area ☀️