Skip to content

Instantly share code, notes, and snippets.

@kean
Last active September 16, 2022 03:41
Show Gist options
  • Star 46 You must be signed in to star a gist
  • Fork 15 You must be signed in to fork a gist
  • Save kean/64b9fc0963fd430594fdb3eb848bccf3 to your computer and use it in GitHub Desktop.
Save kean/64b9fc0963fd430594fdb3eb848bccf3 to your computer and use it in GitHub Desktop.
API Client (Archived)
// The MIT License (MIT)
//
// Copyright (c) 2017 Alexander Grebenyuk (github.com/kean).
import Foundation
import Alamofire
import RxSwift
import RxCocoa
// This post is **archived**. For a modern version that uses Async/Await and Actors, see the new article
// [Web API Client in Swift](/post/new-api-client) (Nov 2021).
protocol ClientProtocol {
func request<Response>(_ endpoint: Endpoint<Response>) -> Single<Response>
}
final class Client: ClientProtocol {
private let manager: Alamofire.SessionManager
private let baseURL = URL(string: "<your_server_base_url>")!
private let queue = DispatchQueue(label: "<your_queue_label>")
init(accessToken: String) {
var defaultHeaders = Alamofire.SessionManager.defaultHTTPHeaders
defaultHeaders["Authorization"] = "Bearer \(accessToken)"
let configuration = URLSessionConfiguration.default
// Add `Auth` header to the default HTTP headers set by `Alamofire`
configuration.httpAdditionalHeaders = defaultHeaders
self.manager = Alamofire.SessionManager(configuration: configuration)
self.manager.retrier = OAuth2Retrier()
}
func request<Response>(_ endpoint: Endpoint<Response>) -> Single<Response> {
return Single<Response>.create { observer in
let request = self.manager.request(
self.url(path: endpoint.path),
method: httpMethod(from: endpoint.method),
parameters: endpoint.parameters
)
request
.validate()
.responseData(queue: self.queue) { response in
let result = response.result.flatMap(endpoint.decode)
switch result {
case let .success(val): observer(.success(val))
case let .failure(err): observer(.error(err))
}
}
return Disposables.create {
request.cancel()
}
}
}
private func url(path: Path) -> URL {
return baseURL.appendingPathComponent(path)
}
}
private func httpMethod(from method: Method) -> Alamofire.HTTPMethod {
switch method {
case .get: return .get
case .post: return .post
case .put: return .put
case .patch: return .patch
case .delete: return .delete
}
}
private class OAuth2Retrier: Alamofire.RequestRetrier {
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
if (error as? AFError)?.responseCode == 401 {
// TODO: implement your Auth2 refresh flow
// See https://github.com/Alamofire/Alamofire#adapting-and-retrying-requests
}
completion(false, 0)
}
}
// The MIT License (MIT)
//
// Copyright (c) 2017 Alexander Grebenyuk (github.com/kean).
import Foundation
// MARK: Defines
typealias Parameters = [String: Any]
typealias Path = String
enum Method {
case get, post, put, patch, delete
}
// MARK: Endpoint
final class Endpoint<Response> {
let method: Method
let path: Path
let parameters: Parameters?
let decode: (Data) throws -> Response
init(method: Method = .get,
path: Path,
parameters: Parameters? = nil,
decode: @escaping (Data) throws -> Response) {
self.method = method
self.path = path
self.parameters = parameters
self.decode = decode
}
}
// MARK: Convenience
extension Endpoint where Response: Swift.Decodable {
convenience init(method: Method = .get,
path: Path,
parameters: Parameters? = nil) {
self.init(method: method, path: path, parameters: parameters) {
try JSONDecoder().decode(Response.self, from: $0)
}
}
}
extension Endpoint where Response == Void {
convenience init(method: Method = .get,
path: Path,
parameters: Parameters? = nil) {
self.init(
method: method,
path: path,
parameters: parameters,
decode: { _ in () }
)
}
}
// The MIT License (MIT)
//
// Copyright (c) 2017 Alexander Grebenyuk (github.com/kean).
import Foundation
// MARK: Defining Endpoints
enum API {}
extension API {
static func getCustomer() -> Endpoint<Customer> {
return Endpoint(path: "customer/profile")
}
static func patchCustomer(name: String) -> Endpoint<Customer> {
return Endpoint(
method: .patch,
path: "customer/profile",
parameters: ["name" : name]
)
}
}
final class Customer: Decodable {
let name: String
}
// MARK: Using Endpoints
func test() {
let client = Client(accessToken: "<access_token>")
_ = client.request(API.getCustomer())
_ = client.request(API.patchCustomer(name: "Alex"))
}
@hiteshjoshi
Copy link

I am getting errors like

load failed with error Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLStringKey=

@kdawgwilk
Copy link

How would go about adding support for Encodable Parameters?

@paweljankowski
Copy link

Great example! But how to return Response with arrays of codable items? :)

@hareshgediya
Copy link

Awesome! How would to add support for custom JSON initialiser?

@alvarocofre
Copy link

I can't understand how you can Chain two requests with this code.
Please, could you help me with an answer.
Thank you

@OhhhThatVarun
Copy link

Great example! But how to return Response with arrays of codable items? :)

Hey! Did you get a way to solve this?

@kean
Copy link
Author

kean commented Nov 21, 2021

The original post and the associated code are archived. For a modern version that uses Async/Await and Actors, see the new article Web API Client in Swift (Nov 2021).

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