Last active
November 24, 2022 21:42
-
-
Save antonyalkmim/3a021a0011e5225a14aaf4c80fff7802 to your computer and use it in GitHub Desktop.
A URLSession approach to organize Network layer
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
| import Foundation | |
| import RxSwift | |
| extension HttpService: ReactiveCompatible { } | |
| extension Reactive where Base: HttpServiceType { | |
| func request(_ endpoint: Base.Target) -> Single<Data> { | |
| return Single<Data>.create(subscribe: { [weak base] single in | |
| let task = base?.request(endpoint, responseData: { result in | |
| switch result { | |
| case Result.success(let data): | |
| single(SingleEvent.success(data)) | |
| case Result.failure(let error): | |
| single(SingleEvent.error(error)) | |
| } | |
| }) | |
| return Disposables.create { task?.cancel() } | |
| }) | |
| } | |
| } | |
| extension PrimitiveSequence where TraitType == SingleTrait, ElementType == Data { | |
| /// Maps received data at key path into a Decodable object. If the conversion fails, the signal errors. | |
| public func map<D: Decodable>(_ type: D.Type, using decoder: JSONDecoder = JSONDecoder()) -> Single<D> { | |
| return flatMap { data in | |
| do { | |
| let res = try decoder.decode(type, from: data) | |
| return Single<D>.just(res) | |
| } catch { | |
| return Single<D>.error(HttpError.jsonMapping(error)) | |
| } | |
| } | |
| } | |
| } | |
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
| // | |
| // HttpService.swift | |
| // antonyalkmim | |
| // | |
| // Created by Antony Alkmim on 15/05/18. | |
| // Copyright © 2018 Antony Alkmim. All rights reserved. | |
| // | |
| import Foundation | |
| protocol TargetType { | |
| var baseURL: URL { get } | |
| var path: String { get } | |
| var method: HttpMethod { get } | |
| var jsonParameters: [String: Any] { get } | |
| var headers: [String: String]? { get } | |
| } | |
| extension TargetType { | |
| func urlRequest() -> URLRequest { | |
| // generate url | |
| let urlPath = [self.baseURL.absoluteString, self.path].joined() | |
| let url = URL(string: urlPath)! | |
| var request = URLRequest(url: url) | |
| /// http method | |
| request.httpMethod = self.method.rawValue | |
| /// body | |
| switch self.method { | |
| case .get: break | |
| default: | |
| request.httpBody = try? JSONSerialization.data(withJSONObject: self.jsonParameters, options: []) | |
| } | |
| /// headers | |
| self.headers?.forEach { it in | |
| request.addValue(it.value, forHTTPHeaderField: it.key) | |
| } | |
| return request | |
| } | |
| } | |
| enum Result { | |
| case failure(Error) | |
| case success(Data) | |
| } | |
| enum HttpMethod: String { | |
| case get = "GET" | |
| case post = "POST" | |
| case put = "PUT" | |
| case delete = "DELETE" | |
| } | |
| enum HttpError: Swift.Error { | |
| case jsonMapping(Error?) | |
| case invalidJson | |
| } | |
| protocol HttpServiceType: class { | |
| associatedtype Target: TargetType | |
| func request(_ endpoint: Target, responseData: @escaping (Result) -> Void) -> URLSessionDataTask | |
| } | |
| class HttpService<Target: TargetType>: HttpServiceType { | |
| private var requestClosure: (Target) -> URLRequest | |
| private var session: URLSession | |
| // MARK: - Initializer | |
| init( | |
| configuration: URLSessionConfiguration = URLSessionConfiguration.default, | |
| requestClosure: @escaping ((Target) -> URLRequest) = { $0.urlRequest() } | |
| ) { | |
| self.session = URLSession(configuration: configuration) | |
| self.requestClosure = requestClosure | |
| } | |
| func request(_ endpoint: Target, responseData: @escaping (Result) -> Void) -> URLSessionDataTask { | |
| //1 - pass through interceptors | |
| let request = requestClosure(endpoint) | |
| //2 - execute task | |
| let task = URLSession.shared.dataTask(with: request) { data, _, error in | |
| guard let data = data else { | |
| responseData(Result.failure(error!)) | |
| return | |
| } | |
| responseData(Result.success(data)) | |
| } | |
| task.resume() | |
| //3 - return task | |
| return task | |
| } | |
| } |
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
| struct Repo: Decodable { | |
| let id: Int | |
| let full_name: String | |
| } | |
| enum GithubAPI { | |
| case getRepos(username: String) | |
| } | |
| extension GithubAPI { | |
| var baseURL: URL { return URL(string: "https://api.github.com")! } | |
| var path: String { | |
| switch self { | |
| case .getRepos(let username): | |
| return "/users/\(username)/repos" | |
| } | |
| } | |
| var method: HttpMethod { | |
| switch self { | |
| case .getRepos: return .get | |
| } | |
| } | |
| var jsonParameters: [String: Any] { | |
| return [:] | |
| } | |
| var headers: [String: String]? { | |
| return ["Content-type": "application/json"] | |
| } | |
| } | |
| /// Usage Example | |
| let api = HttpService<GithubAPI>(requestClosure: { endpoint in | |
| var request = endpoint.urlRequest() | |
| request.setValue("Authorization", forHTTPHeaderField: "Bearer ACCESS_TOKEN") | |
| return request | |
| }) | |
| api.rx.request(.getRepos(username: "antonyalkmim")) | |
| .map(Repo.self) | |
| .subscribe(onSuccess: { repos in | |
| print(token) | |
| }) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TODO