Snippets

iOS Networking

Here the snippets I keep reusing when I build iOS apps that need to communicate with a HTTP/JSON api.

Endpoint

This snippet aims to generalize an HTTP endpoint.

protocol Endpoint {
    var base: String { get }
    var path: String { get }
    var method: HTTPMethod { get }
    var headers: [String: String] { get }
    var body: Encodable { get }
    var queryParameters: [URLQueryItem] { get }
}

extension Endpoint {
    private var url: URL {
        var components = URLComponents()
        components.scheme = "https"
        components.host = base
        components.path = path
        components.queryItems = queryParameters

        guard let url = components.url else {
            preconditionFailure("Invalid URL components: \(components)")
        }
        return url
    }

    var urlRequest: URLRequest {
            var request = URLRequest(url: self.url)
            request.httpMethod = self.method.rawValue
            
            self.headers.forEach { (k, v) in
                request.setValue(v, forHTTPHeaderField: k)
            }
            
            if self.method == HTTPMethod.post || self.method == .put {
                do {
                    let encoder = JSONEncoder()
                    request.httpBody = try encoder.encode(self.body)
                } catch {
                    print(error.localizedDescription)
                }
            }
            
            return request
        }
}

enum HTTPMethod: String {
    case post = "POST"
    case get = "GET"
    case put = "PUT"
    case patch = "PATCH"
    case delete = "DELETE"
    case head = "HEAD"
    case options = "OPTIONS"
}

The following snippet performs the actual HTTP requests for a given endpoint:

enum NetworkingError: Error {
    case networkError
    case decodingError
    case notfoundError
    case apiError
}

@MainActor
struct Networking {    
    func performRequest<T: Decodable>(endpoint: Endpoint, decoder: JSONDecoder = JSONDecoder()) async ->  Result<T, NetworkingError>  {
        
        do {
            let (data, response) = try await URLSession.shared.data(for: endpoint.urlRequest)
            
            guard let httpResponse = response as? HTTPURLResponse else {
                return .failure(.networkError)
            }
            
            if !(200...299).contains(httpResponse.statusCode) {
                return .failure(.apiError)
            }
            
            do {
                let result = try decoder.decode(T.self, from: data)
                return .success(result)
                
            } catch(let error) {
                return .failure(.decodingError)
            }
        } catch {
            return .failure(.networkError)
        }
    }
}

And that’s pretty much it. To use these snippets, I create a new struct or class that implements the Endpoint protocol and uses the performRequest from the networking struct to make the api call.