Skip to content

Instantly share code, notes, and snippets.

@Stmol
Created March 29, 2019 11:19
Show Gist options
  • Save Stmol/0b0a3a14b8a66d8f9785c1a3f5449146 to your computer and use it in GitHub Desktop.
Save Stmol/0b0a3a14b8a66d8f9785c1a3f5449146 to your computer and use it in GitHub Desktop.
API Client with pure URLSession
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