http://objc.to/A#https://www.vadimbulavin.com/modern-networking-in-swift-5-with-urlsession-combine-framework-and-codable/

Making HTTP requests is one of first things to learn when starting iOS development. Whether you implement networking from scratch, or use Alamofire and Moya, you often end up with a complex and tangled code. Especially, when it comes to requests chaining, running in parallel or cancelling.

Swift 5 system frameworks already provide us with all the tools that we need to write concise networking layer. In this article we’ll implement a promise-based networking agent by using vanilla Swift 5 APIs: CodableURLSession and the Combine framework. To battle-test our networking layer, we’ll practice with several real-world examples that query Github REST API and synchronize the HTTP requests in chain and in parallel.

Implementing Networking Agent

Agent is a promise-based HTTP client. It fulfills and configures requests by passing a single URLRequest object to it. The agent automatically transforms JSON data into a Codable value and returns an AnyPublisher instance:

import Combine

struct Agent {    
    // 1
    struct Response<T> {
        let value: T
        let response: URLResponse
    }
    
    // 2
    func run<T: Decodable>(_ request: URLRequest, _ decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher<Response<T>, Error> {
        return URLSession.shared
            .dataTaskPublisher(for: request) // 3
            .tryMap { result -> Response<T> in
                let value = try decoder.decode(T.self, from: result.data) // 4
                return Response(value: value, response: result.response) // 5
            }
            .receive(on: DispatchQueue.main) // 6
            .eraseToAnyPublisher() // 7
    }
}

The code requires some basic understanding of Combine. Here is the bird’s-eye overview of the Swift Combine framework.

  1. Response<T> carries both parsed value and a URLResponse instance. The latter can be used for status code validation and logging.
  2. The run<T>() method is the single entry point for requests execution. It accepts a URLRequest instance that fully describes the request configuration. The decoder is optional in case custom JSON parsing is needed.
  3. Create data task as a Combine publisher.
  4. Parse JSON data. We have constrained T to be Decodable in the run<T>() method declaration.
  5. Create the Response<T> object and pass it downstream. It contains the parsed value and the URL response.
  6. Deliver values on the main thread.
  7. Erase publisher’s type and return an instance of AnyPublisher.

After implementing the networking core, we are ready to tackle several real-world examples.

Adding HTTP Request