Last active
September 16, 2022 03:41
-
-
Save kean/64b9fc0963fd430594fdb3eb848bccf3 to your computer and use it in GitHub Desktop.
API Client (Archived)
This file contains hidden or 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
// 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) | |
} | |
} |
This file contains hidden or 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
// 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 () } | |
) | |
} | |
} |
This file contains hidden or 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
// 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")) | |
} |
Great example! But how to return Response with arrays of codable items? :)
Hey! Did you get a way to solve this?
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
I can't understand how you can Chain two requests with this code.
Please, could you help me with an answer.
Thank you