Result type

Purpose

The response from a network call provides us with data or error.

We recover fruits with the following model :

struct FruitsResponse: Decodable {
    let results: [Fruit]
}

struct Fruit: Decodable, Equatable, Identifiable {
    let id: Int
    let name: String
    let overview: String
}

In a network call with a classic completion like :

func getFruits(_ completion: @escaping (FruitsResponse?, Error?) -> Void) {}

there are 4 possibilities:

  • completion(fruitsResponse, nil)
  • completion(nil, error)
  • completion(fruitsResponse, error)
  • completion(nil, nil)

completion(nil, nil) or completion(fruitsResponse, error) doesn't make sense because we expect either a response with correct data or an error.

Thus we can use the Result type:

func getFruits(_ completion: @escaping (Result<FruitsResponse, Error>) -> Void) {}

The Result type is an enum with two cases:

  • success (completion(.success(decoded)))
  • failure (completion(.failure(error!)))

we have either a success or a failure, not both.

Example


For a classic call :

func getFruits(_ completion: @escaping (FruitsResponse?, Error?) -> Void) {
    let url : URL(string: "https://-------")
    URLSession.shared.dataTask(with: url) { data, _, error in
        guard error == nil else {
            completion(nil, error)
            return
        }
      do {
        let decoded = try jsonDecoder.decode(FruitsResponse.self, from: data!)
        completion(decoded, nil)
      } catch {
        completion(nil, error)
      }    
    }.resume()
}

The response is retrieved at the controller :

getFruits { [weak self] fruitsResponse, _ in
    guard let fruitsResponse = fruitsResponse else { return }
    DispatchQueue.main.async {
        self.data = fruitsResponse.results
    }

using Result, we obtain :

func getFruits(_ completion: @escaping (Result<FruitsResponse, Error>) -> Void) {
    let url : URL(string: "    ")
    URLSession.shared.dataTask(with: url) { data, _, error in
        guard error == nil else {
            completion(.failure(error!))
            return
        }
        do {
            let decoded = try jsonDecoder.decode(FruitsResponse.self, from: data!)
            completion(.success(decoded))
        } catch {
            completion(.failure(error))
        }
    }.resume()
}

The call in the controller becomes :

getFruits { [weak self] result in
        switch result {
            case let .success(fruitsResponse):
                DispatchQueue.main.async {
                    self?.movies = fruitsResponse.results
                }
            case .failure:
                break
        }
    }

We are obliged to treat both cases of the enum.


Thanks for reading and have fun coding