Skip to content

Instantly share code, notes, and snippets.

@TyrfingMjolnir
Forked from vinczebalazs/NetworkingExample.swift
Last active November 7, 2022 12:17
Show Gist options
  • Save TyrfingMjolnir/a9481948040e324d0a6f0c664e546b23 to your computer and use it in GitHub Desktop.
Save TyrfingMjolnir/a9481948040e324d0a6f0c664e546b23 to your computer and use it in GitHub Desktop.
Protocol and generic based networking example.
import Foundation
// Model.
struct Employee: Codable {
let id: Int
let name: String
}
// Networking.
enum HTTPMethod: String {
case delete = "DELETE" // Delete resource
case get = "GET" // Get anything, or specific resource
case patch = "PATCH" // Modify an resource's values while iterating through its keys
case post = "POST" // Create resource
case put = "PUT" // Replace resource usually done by inserting the payload as a replacement of the current resource.
case head = "HEAD" // The HEAD method asks for a response identical to a GET request, but without the response body.
case connect = "CONNECT" // The CONNECT method establishes a tunnel to the server identified by the target resource.
case options = "OPTIONS" // The OPTIONS method describes the communication options for the target resource.
case trace = "TRACE" // The TRACE method performs a message loop-back test along the path to the target resource.
}
protocol APIRequest {
associatedtype Result
var endpoint: String { get }
var method: HTTPMethod { get }
}
enum APIError: Error {
case invalidResponse
case httpError(Int)
}
final class APIClient {
// MARK: Public Properties
static let shared = APIClient()
// MARK: Private Properties
private let baseURL = URL(string: "https://example.com")!
private lazy var urlSession: URLSession = {
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = [
"Accept": "application/json",
"Content-Type": "application/json"]
return URLSession(configuration: config)
}()
// MARK: Initializers
private init() {}
// MARK: Public Methods
func execute<RequestType: APIRequest>(_ request: RequestType,
completion: @escaping (Result<RequestType.Result, Error>) -> ())
where RequestType.Result: Decodable {
urlSession.dataTask(with: urlRequest(for: request)) { (data, response, error) in
if let error = error {
completion(.failure(error))
} else {
if let urlResponse = response as? HTTPURLResponse {
if urlResponse.statusCode >= 400 {
completion(.failure(APIError.httpError(urlResponse.statusCode)))
} else {
do {
completion(.success(try JSONDecoder().decode(RequestType.Result.self, from: data!)))
} catch let _error {
completion(.failure(_error))
}
}
} else {
completion(.failure(APIError.invalidResponse))
}
}
}.resume()
}
// MARK: Private Methods
private func urlRequest<RequestType: APIRequest>(for request: RequestType) -> URLRequest {
var urlComponents = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)!
urlComponents.path = "/\(request.endpoint)"
var urlRequest = URLRequest(url: urlComponents.url!)
urlRequest.httpMethod = request.method.rawValue
return urlRequest
}
}
// Example usage.
struct EmployeesRequest: APIRequest {
typealias Result = [Employee]
let endpoint = "employees"
let method = HTTPMethod.get
}
APIClient.shared.execute(EmployeesRequest(), completion: {
switch $0 {
case let .success(result):
print(result)
case let .failure(error):
print(error)
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment