Skip to content

Instantly share code, notes, and snippets.

@yoxisem544
Created June 1, 2020 05:06
Show Gist options
  • Save yoxisem544/69d4940e8276820a8cb9df168feca9b7 to your computer and use it in GitHub Desktop.
Save yoxisem544/69d4940e8276820a8cb9df168feca9b7 to your computer and use it in GitHub Desktop.
NetworkRequest wrapper of Alamofire 5
//
// 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)
}
}
}
//
// 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