Forked from vinczebalazs/NetworkingExample.swift
Last active
November 7, 2022 12:17
-
-
Save TyrfingMjolnir/a9481948040e324d0a6f0c664e546b23 to your computer and use it in GitHub Desktop.
Protocol and generic based networking example.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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