Created
June 1, 2020 05:06
-
-
Save yoxisem544/69d4940e8276820a8cb9df168feca9b7 to your computer and use it in GitHub Desktop.
NetworkRequest wrapper of Alamofire 5
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
// | |
// NetworkRequest.swift | |
// | |
// Created by David on 2020/5/31. | |
// Copyright © 2020 David. All rights reserved. | |
// | |
import Alamofire | |
public struct NetworkRequest<Response: Decodable> { | |
public let endpoint: String | |
public let method: HTTPMethod | |
public let parameters: [String: Any]? | |
public let encoding: ParameterEncoding | |
public let headers: HTTPHeaders? | |
public let interceptor: RequestInterceptor? | |
public let decoding: (Data) throws -> Response | |
public init( | |
endpoint: String, | |
method: HTTPMethod = .get, | |
parameters: [String: Any]? = nil, | |
encoding: ParameterEncoding = URLEncoding.default, | |
headers: HTTPHeaders? = nil, | |
interceptor: RequestInterceptor? = nil, | |
decoding: ((Data) throws -> Response)? = nil | |
) { | |
self.endpoint = { | |
if endpoint.hasPrefix("/") { | |
return endpoint | |
} else { | |
return "/" + endpoint | |
} | |
}() | |
self.method = method | |
self.parameters = parameters | |
self.encoding = encoding | |
self.headers = headers | |
self.interceptor = interceptor | |
self.decoding = decoding ?? Response.defaultDecoding | |
} | |
} | |
fileprivate extension Decodable { | |
static var defaultDecoding: (Data) throws -> Self { | |
return { data in | |
return try JSONDecoder().decode(self, from: data) | |
} | |
} | |
} |
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
// | |
// NetworkRequestSender.swift | |
// | |
// Created by David on 2020/5/31. | |
// Copyright © 2020 David. All rights reserved. | |
// | |
import Alamofire | |
import PromiseKit | |
public struct NetworkRequestSender { | |
/// Session to be used while creating network requests. | |
private let session: Session | |
/// Base url of `NetworkRequestSender` | |
public let baseURLString: String | |
// MARK: - Initialization | |
/// Creates a `NetworkRequestSender` from a `URLSessionConfiguration`. | |
/// | |
/// - Note: This initializer lets Alamofire handle the creation of the underlying `URLSession` and its | |
/// `delegateQueue`, and is the recommended initializer for most uses. | |
/// | |
/// - Parameters: | |
/// - baseURLString: Base url that this session will base on. | |
/// - configuration: `URLSessionConfiguration` to be used to create the underlying `URLSession`. Changes | |
/// to this value after being passed to this initializer will have no effect. | |
/// `URLSessionConfiguration.af.default` by default. | |
/// - delegate: `SessionDelegate` that handles `session`'s delegate callbacks as well as `Request` | |
/// interaction. `SessionDelegate()` by default. | |
/// - rootQueue: Root `DispatchQueue` for all internal callbacks and state updates. **MUST** be a | |
/// serial queue. `DispatchQueue(label: "org.alamofire.session.rootQueue")` by default. | |
/// - startRequestsImmediately: Determines whether this instance will automatically start all `Request`s. `true` | |
/// by default. If set to `false`, all `Request`s created must have `.resume()` called. | |
/// on them for them to start. | |
/// - requestQueue: `DispatchQueue` on which to perform `URLRequest` creation. By default this queue | |
/// will use the `rootQueue` as its `target`. A separate queue can be used if it's | |
/// determined request creation is a bottleneck, but that should only be done after | |
/// careful testing and profiling. `nil` by default. | |
/// - serializationQueue: `DispatchQueue` on which to perform all response serialization. By default this | |
/// queue will use the `rootQueue` as its `target`. A separate queue can be used if | |
/// it's determined response serialization is a bottleneck, but that should only be | |
/// done after careful testing and profiling. `nil` by default. | |
/// - interceptor: `RequestInterceptor` to be used for all `Request`s created by this instance. `nil` | |
/// by default. | |
/// - serverTrustManager: `ServerTrustManager` to be used for all trust evaluations by this instance. `nil` | |
/// by default. | |
/// - redirectHandler: `RedirectHandler` to be used by all `Request`s created by this instance. `nil` by | |
/// default. | |
/// - cachedResponseHandler: `CachedResponseHandler` to be used by all `Request`s created by this instance. | |
/// `nil` by default. | |
/// - eventMonitors: Additional `EventMonitor`s used by the instance. Alamofire always adds a | |
/// `AlamofireNotifications` `EventMonitor` to the array passed here. `[]` by default. | |
public init( | |
baseURLString: String, | |
configuration: URLSessionConfiguration, | |
delegate: SessionDelegate, | |
rootQueue: DispatchQueue, | |
startRequestsImmediately: Bool = true, | |
requestQueue: DispatchQueue? = nil, | |
serializationQueue: DispatchQueue? = nil, | |
interceptor: RequestInterceptor? = nil, | |
serverTrustManager: ServerTrustManager? = nil, | |
redirectHandler: RedirectHandler? = nil, | |
cachedResponseHandler: CachedResponseHandler? = nil, | |
eventMonitors: [EventMonitor] = [] | |
) { | |
self.baseURLString = { | |
if baseURLString.hasSuffix("/") { | |
return String(baseURLString[baseURLString.startIndex..<baseURLString.index(baseURLString.endIndex, offsetBy: -1)]) | |
} else { | |
return baseURLString | |
} | |
}() | |
self.session = Session( | |
configuration: configuration, | |
delegate: delegate, | |
rootQueue: rootQueue, | |
startRequestsImmediately: startRequestsImmediately, | |
requestQueue: requestQueue, | |
serializationQueue: serializationQueue, | |
interceptor: interceptor, | |
serverTrustManager: serverTrustManager, | |
redirectHandler: redirectHandler, | |
cachedResponseHandler: cachedResponseHandler, | |
eventMonitors: eventMonitors | |
) | |
} | |
public init(baseURLString: String) { | |
self.init( | |
baseURLString: baseURLString, | |
configuration: URLSessionConfiguration.af.default, | |
delegate: SessionDelegate(), | |
rootQueue: DispatchQueue(label: "io.network_request_sender.root_queue") | |
) | |
} | |
// MARK: - Public methods | |
/// Send a network request via sender | |
/// | |
/// - Parameter request: a request to be sent | |
/// - Returns: DataRequest | |
public func send<Response: Decodable>(_ request: NetworkRequest<Response>, complete: ((ServerResponse<Response>) -> Void)?) { | |
let convertible = RequestJSONDictionaryConvertible(baseURL: baseURLString, request: request) | |
session.request(convertible, interceptor: request.interceptor).responseData(completionHandler: { data in | |
let result: ServerResponse<Response> = { | |
if data.error == nil, let value = data.value { | |
do { | |
let response = try request.decoding(value) | |
return ServerResponse(response: response, error: data.error, data: data.data) | |
} catch { | |
let error = AFError.responseSerializationFailed(reason: .decodingFailed(error: error)) | |
return ServerResponse<Response>(response: nil, error: error, data: data.data) | |
} | |
} else { | |
return ServerResponse<Response>(response: nil, error: data.error, data: data.data) | |
} | |
}() | |
complete?(result) | |
}) | |
} | |
public func send<Response: Decodable>(_ request: NetworkRequest<Response>) -> Promise<Response> { | |
let convertible = RequestJSONDictionaryConvertible(baseURL: baseURLString, request: request) | |
return Promise<Data> { seal in | |
session.request(convertible, interceptor: request.interceptor).responseData(completionHandler: { data in | |
if data.error == nil, let value = data.value { | |
seal.fulfill(value) | |
} else if let error = data.error { | |
seal.reject(error) | |
} else { | |
seal.resolve(data.data, data.error) | |
} | |
}) | |
}.map(request.decoding) | |
} | |
public struct ServerResponse<Response> { | |
/// Decoded server response type, nil if error or failed to decode | |
public let response: Response? | |
/// Returns the associated error value if the result if it is a failure, `nil` otherwise. | |
public let error: AFError? | |
/// The data returned by the server. | |
public let data: Data? | |
public init(response: Response?, error: AFError?, data: Data?) { | |
self.response = response | |
self.error = error | |
self.data = data | |
} | |
} | |
/// Converts `NetworkRequest` into `URLRequestConvertible` in order to have a paramter as JSON Dictionary format. | |
private struct RequestJSONDictionaryConvertible: Alamofire.URLRequestConvertible { | |
let url: URLConvertible | |
let method: HTTPMethod | |
let parameters: [String: Any]? | |
let encoding: ParameterEncoding | |
let headers: HTTPHeaders? | |
func asURLRequest() throws -> URLRequest { | |
let request = try URLRequest(url: url, method: method, headers: headers) | |
if let parameters = parameters { | |
return try request.encoded(parameters: parameters, parameterEncoding: encoding) | |
} else { | |
return request | |
} | |
} | |
init<Response: Decodable>(baseURL: String, request: NetworkRequest<Response>) { | |
self.url = (baseURL + request.endpoint) | |
self.method = request.method | |
self.parameters = request.parameters | |
self.encoding = request.encoding | |
self.headers = request.headers | |
} | |
} | |
} | |
fileprivate extension URLRequest { | |
/// Helper extension to encode JSON dictoinary into URLRequest. | |
/// | |
/// - Parameters: | |
/// - parameters: JSON dictionary params to encode. | |
/// - parameterEncoding: Encoding type of params. Can be JSONEncoding or URLEncoding. | |
/// - Throws: error if params failed to encode. | |
/// - Returns: URLRequest with encoded params. | |
func encoded(parameters: [String: Any], parameterEncoding: ParameterEncoding) throws -> URLRequest { | |
do { | |
return try parameterEncoding.encode(self, with: parameters) | |
} catch { | |
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment