Skip to content

Instantly share code, notes, and snippets.

@sarpsolakoglu
Last active June 30, 2020 16:38
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save sarpsolakoglu/9033a319d5a199b4d702a094770f3afa to your computer and use it in GitHub Desktop.
Save sarpsolakoglu/9033a319d5a199b4d702a094770f3afa to your computer and use it in GitHub Desktop.
Simple Swift 4 Rest Client that uses the Codable protocol for JSON conversion
//
// SimpleClient.swift
//
// Created by Sarp Solakoglu on 18/09/2017.
// Copyright © 2017 Sarp Solakoglu. All rights reserved.
//
import Foundation
enum RestMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
}
struct EmptyRequest: Encodable {}
struct EmptyResponse: Decodable {}
protocol SimpleClient {
var baseURL: URL { get }
var session: URLSession { get }
func get<D: Decodable>(_ responseType: D.Type, endpoint: String, params: [String : String]?, headers: [String : String]?, completion: @escaping (D?, URLResponse?, Error?) -> Void)
func post<E: Encodable, D: Decodable>(_ responseType: D.Type, endpoint: String, params: [String : String]?, body: E?, headers: [String : String]?, completion: @escaping (D?, URLResponse?, Error?) -> Void)
func put<E: Encodable, D: Decodable>(_ responseType: D.Type, endpoint: String, params: [String : String]?, body: E?, headers: [String : String]?, completion: @escaping (D?, URLResponse?, Error?) -> Void)
func delete<E: Encodable, D: Decodable>(_ responseType: D.Type, endpoint: String, params: [String : String]?, body: E?, headers: [String : String]?, completion: @escaping (D?, URLResponse?, Error?) -> Void)
func performRequest<D: Decodable>(_ responseType: D.Type, request: URLRequest, completion: @escaping (D?, URLResponse?, Error?) -> Void)
}
class DefaultClient {
let baseURL: URL
let session: URLSession
init(baseURL: URL) {
self.baseURL = baseURL
self.session = URLSession(configuration: URLSessionConfiguration.default)
}
}
extension DefaultClient: SimpleClient {
func get<D: Decodable>(_ responseType: D.Type, endpoint: String, params: [String : String]?, headers: [String : String]?, completion: @escaping (D?, URLResponse?, Error?) -> Void) {
let url = baseURL.addEndpoint(endpoint: endpoint).addParams(params: params)
let request = self.buildRequest(url: url, method: RestMethod.get.rawValue, headers: headers, body: EmptyRequest())
self.performRequest(responseType, request: request, completion: completion)
}
func post<E: Encodable, D: Decodable>(_ responseType: D.Type, endpoint: String, params: [String : String]?, body: E?, headers: [String : String]?, completion: @escaping (D?, URLResponse?, Error?) -> Void) {
let url = baseURL.addEndpoint(endpoint: endpoint).addParams(params: params)
let request = self.buildRequest(url: url, method: RestMethod.post.rawValue, headers: headers, body: body)
self.performRequest(responseType, request: request, completion: completion)
}
func put<E: Encodable, D: Decodable>(_ responseType: D.Type, endpoint: String, params: [String : String]?, body: E?, headers: [String : String]?, completion: @escaping (D?, URLResponse?, Error?) -> Void) {
let url = baseURL.addEndpoint(endpoint: endpoint).addParams(params: params)
let request = self.buildRequest(url: url, method: RestMethod.put.rawValue, headers: headers, body: body)
self.performRequest(responseType, request: request, completion: completion)
}
func delete<E: Encodable, D: Decodable>(_ responseType: D.Type, endpoint: String, params: [String : String]?, body: E?, headers: [String : String]?, completion: @escaping (D?, URLResponse?, Error?) -> Void) {
let url = baseURL.addEndpoint(endpoint: endpoint).addParams(params: params)
let request = self.buildRequest(url: url, method: RestMethod.delete.rawValue, headers: headers, body: body)
self.performRequest(responseType, request: request, completion: completion)
}
func performRequest<D: Decodable>(_ responseType: D.Type, request: URLRequest, completion: @escaping (D?, URLResponse?, Error?) -> Void) {
self.session.dataTask(with: request) { (data, response, error) in
if error != nil || data == nil {
completion(nil, response, error)
} else {
guard let responseData = data else {
completion(nil, response, error)
return
}
guard error == nil else {
completion(nil, response, error!)
return
}
let decoder = JSONDecoder()
do {
let result = try decoder.decode(D.self, from: responseData)
completion(result, response, nil)
} catch {
completion(nil, response, error)
}
}
}.resume()
}
private func buildRequest<E: Encodable>(url: URL, method: String, headers: [String: String]?, body: E?) -> URLRequest {
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = method
if let requestHeaders = headers {
for (key, value) in requestHeaders {
request.addValue(value, forHTTPHeaderField: key)
}
}
if let requestBody = body {
if !(requestBody is EmptyRequest) {
let encoder = JSONEncoder();
request.httpBody = try? encoder.encode(requestBody)
}
}
return request
}
}
fileprivate extension URL {
func addEndpoint(endpoint: String) -> URL {
return URL(string: endpoint, relativeTo: self)!
}
func addParams(params: [String: String]?) -> URL {
guard let params = params else {
return self
}
var urlComp = URLComponents(url: self, resolvingAgainstBaseURL: true)!
var queryItems = [URLQueryItem]()
for (key, value) in params {
queryItems.append(URLQueryItem(name: key, value: value))
}
urlComp.queryItems = queryItems
return urlComp.url!
}
}
@abdbinrus93
Copy link

I liked your code. Could you show an example request?

@k9doghouse
Copy link

Thanks!

@Deco354
Copy link

Deco354 commented Mar 26, 2019

Is there not an encapsulation issue here? I can create a class conforming to this protocol with methods like MovieDBAPI.getMovies() but MovieDBAPI.get(), put, post, session, baseURL and all the other internal details of our object will be available as we can't make the protocol private if we wish to use it and the members of that protocol must match its access control.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment