Created
March 29, 2019 11:19
-
-
Save Stmol/0b0a3a14b8a66d8f9785c1a3f5449146 to your computer and use it in GitHub Desktop.
API Client with pure URLSession
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
enum HTTPMethod: String { | |
case get = "GET" | |
case put = "PUT" | |
case post = "POST" | |
case delete = "DELETE" | |
case head = "HEAD" | |
case options = "OPTIONS" | |
case trace = "TRACE" | |
case connect = "CONNECT" | |
} | |
struct HTTPHeader { | |
let field: String | |
let value: String | |
} | |
class APIRequest { | |
let method: HTTPMethod | |
let path: String | |
var queryItems: [URLQueryItem]? | |
var headers: [HTTPHeader]? | |
var body: Data? | |
init(method: HTTPMethod, path: String) { | |
self.method = method | |
self.path = path | |
} | |
init<Body: Encodable>(method: HTTPMethod, path: String, body: Body) throws { | |
self.method = method | |
self.path = path | |
self.body = try JSONEncoder().encode(body) | |
} | |
} | |
struct APIResponse<Body> { | |
let statusCode: Int | |
let body: Body | |
} | |
extension APIResponse where Body == Data? { | |
func decode<BodyType: Decodable>(to type: BodyType.Type) throws -> APIResponse<BodyType> { | |
guard let data = body else { | |
throw APIError.decodingFailure | |
} | |
let decodedJSON = try JSONDecoder().decode(BodyType.self, from: data) | |
return APIResponse<BodyType>(statusCode: self.statusCode, | |
body: decodedJSON) | |
} | |
} | |
enum APIError: Error { | |
case invalidURL | |
case requestFailed | |
case decodingFailure | |
} | |
enum APIResult<Body> { | |
case success(APIResponse<Body>) | |
case failure(APIError) | |
} | |
struct APIClient { | |
typealias APIClientCompletion = (APIResult<Data?>) -> Void | |
private let session = URLSession.shared | |
private let baseURL = URL(string: "https://jsonplaceholder.typicode.com")! | |
func perform(_ request: APIRequest, _ completion: @escaping APIClientCompletion) { | |
var urlComponents = URLComponents() | |
urlComponents.scheme = baseURL.scheme | |
urlComponents.host = baseURL.host | |
urlComponents.path = baseURL.path | |
urlComponents.queryItems = request.queryItems | |
guard let url = urlComponents.url?.appendingPathComponent(request.path) else { | |
completion(.failure(.invalidURL)); return | |
} | |
var urlRequest = URLRequest(url: url) | |
urlRequest.httpMethod = request.method.rawValue | |
urlRequest.httpBody = request.body | |
request.headers?.forEach { urlRequest.addValue($0.value, forHTTPHeaderField: $0.field) } | |
let task = session.dataTask(with: url) { (data, response, error) in | |
guard let httpResponse = response as? HTTPURLResponse else { | |
completion(.failure(.requestFailed)); return | |
} | |
completion(.success(APIResponse<Data?>(statusCode: httpResponse.statusCode, body: data))) | |
} | |
task.resume() | |
} | |
} | |
/// MARK: - Usage - | |
struct Post: Decodable { | |
let userId: Int | |
let id: Int | |
let title: String | |
let body: String | |
} | |
let request = APIRequest(method: .get, path: "posts") | |
APIClient().perform(request) { (result) in | |
switch result { | |
case .success(let response): | |
if let response = try? response.decode(to: [Post].self) { | |
let posts = response.body | |
print("Received posts: \(posts.first?.title ?? "")") | |
} else { | |
print("Failed to decode response") | |
} | |
case .failure: | |
print("Error perform network request") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment