几乎全部的项目都须要网络请求,由于他能够给用户呈现更加丰富的内容,方便咱们在不一样设备之间管理同步数据。网络请求会出如今你项目的各个地方:启动页,列表页,登陆注册...因此如何管理组织网络请求是 App 架构中很是重要的一部分。Github 上也有相似的框架好比 Moya
, 咱们且称其为网络框架的框架吧。Moya
也有这个框架也发展了好久了,功能很强大,社区也一直很活跃,也有衍生的 RxMoya
和 ReactiveMoya
。但我在使用事后发现他过于 重
了,一直以为他那种 path
+ method
+ parameter
拆开的写法太过于繁琐,那么本文就让咱们来一步步搭建适合本身的网络框架吧。git
提示:为了方便和通用,咱们的网络请求 API 就直接基于 Alamofire 来写好了。github
首先咱们来看看最简单的请求长什么样?编程
AF.request("https://httpbin.org/get").response { response in
debugPrint(response)
}
复制代码
很是简单对不对?但现实并非这样,在一个请求中咱们须要处理各类疑难杂症,最终一个请求的代码可能会很长很长(长到一个屏幕都放不下!),因此咱们要尽可能抽象和复用这里的逻辑。json
如何下手呢?解决一个问题的最经常使用的方法就是先看清楚问题,而后把大问题拆成小问题,再一个个解决。咱们先来思考下🤔,一个完整请求要作的事情什么:swift
HTTP Request
对象,HTTP Header
HTTP Request
回来的 data 数据error
和 response code
codable
之类的框架将 raw data
转换 model
对象在常规的业务中,二、三、四、5 每每是能够统一抽象处理的,而最多见作法就是用一个 HTTPClient
或 APIManager
来统一 handle 这类逻辑了。而对于 1
每一个请求的参数、地址、方法都不同因此咱们仍是会将他们暴露出去,最终大概长这样:api
warning:下面方法只是替提供思路,部分代码被省略网络
class HTTPClient {
var host: String
init(host: String) {
self.host = host
}
// 设置 timeout
private let sessionManager: SessionManager = {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 15
let sessionManager = SessionManager(configuration: config)
return sessionManager
}()
// 设置 HTTPHeaders
private var defaultHeader: HTTPHeaders = {
var defaultHTTPHeaders = SessionManager.defaultHTTPHeaders
defaultHTTPHeaders["User-Agent"] = "You device user agent"
defaultHTTPHeaders["Accept-Encoding"] = acceptEncoding
// 在 header 中添加 token
return defaultHTTPHeaders
}()
}
extension HTTPClient {
@discardableResult
func requestObject<T: Codable>(path: String, method: Alamofire.HTTPMethod = .get, parameters:[String:Any?]?, handler: @escaping (T?, Error?) -> Void) -> Request {
// json -> model
return buidRequest(path: path, method: method, parameters: parameters) { [weak self](dataSource, error) in
if let error = error {
handler(nil, error)
return
}
// 经过 `codable` 框架将 raw data 转换 model 对象
do {
let model = try dataSource.data?.mapObject(Response<T>.self).data
handler(model, nil)
} catch let error {
let parseError = HTTPClientError(code:.decodeError,localDescrition:"parse_error".localized)
self?.showDecodingError(path: path, error: error)
handler(nil, parseError)
}
}
}
}
// MARK: - Private Methods
private extension HTTPClient {
/// note: data used by codable
typealias CompletionHandler = ((data: Data?, result: Any?), Error?) -> Void
@discardableResult
private func buidRequest(path:String, method: Alamofire.HTTPMethod, parameters:[String:Any?]?, handler: @escaping CompletionHandler) -> Request {
// filter nil value
let validParas = parameters?.compactMapValues { $0 }
let request = sessionManager.request(host + path, method: method, parameters: validParas, headers: defaultHeader)
return request.responseJSON { response in
// 4. 处理 error 和 response code
self.handelResponse(response: response, handler: handler)
}
}
}
复制代码
最后咱们发起请求的方法大概长这样:session
static func auth(from: String, token: String) -> AuthResult? {
let path = "wp-json/wc/v3/third/party/access/token"
let parameters = ["from": from, "third_access_token": token]
return HTTPClient.shared.requestObject(path: path, parameters: parameters)
}
复制代码
不会 RxSwift
建议你们都去学一啦,响应式编程真的很棒棒数据结构
extension HTTPClient: ReactiveCompatible {}
extension Reactive where Base: HTTPClient {
/// Designated request-making method.
///
/// - Parameters:
/// - path: url path
/// - parameters: A dictionary of parameters to apply to a `URLRequest`
/// - Returns: Response of singleobject.
func requestObject<T: Codable>(path:String, method: HTTPMethod = .get, parameters:[String:Any?]?) -> Single<T?> {
return Single.create { single in
let request = self.base.requestObject(path: path, method: method, parameters: parameters, handler: { (model: T?, error) in
if let error = error {
single(.error(error))
} else {
single(.success(model))
}
})
return Disposables.create {
request.cancel()
}
}
}
}
复制代码
得益与 RxSwift 重试和请求合并不是常简单。架构
// 请求合并
Observable.zip(request1, request2, request3)
.subscribe(onNext: { (resp1, resp2, resp3) in
})
.disposed(by: disposeBag)
// 请求重试
HTTPClient.rx.user()
.asObservable()
.catchErrorJustReturn(nil)
.retry(3, delay: .constant(time: 3))
.disposed(by: disposeBag)
// RxSwift+Retry
enum DelayOptions {
case immediate
case constant(time: Double)
case exponential(initial: Double, multiplier: Double, maxDelay: Double)
case custom(closure: (Int) -> Double)
}
extension DelayOptions {
func make(_ attempt: Int) -> Double {
switch self {
case .immediate: return 0.0
case .constant(let time): return time
case .exponential(let initial, let multiplier, let maxDelay):
// if it's first attempt, simply use initial delay, otherwise calculate delay
let delay = attempt == 1 ? initial : initial * pow(multiplier, Double(attempt - 1))
return min(maxDelay, delay)
case .custom(let closure): return closure(attempt)
}
}
}
/// 主要是用于网络请求的重试,能够设置重试次数,重试之间的间隔,以及有网络开始重试的逻辑
/// reference:http://kean.github.io/post/smart-retry
extension ObservableType {
/// Retries the source observable sequence on error using a provided retry
/// strategy.
/// - parameter maxAttemptCount: Maximum number of times to repeat the
/// sequence. `Int.max` by default.
/// - parameter didBecomeReachable: Trigger which is fired when network
/// connection becomes reachable.
/// - parameter shouldRetry: Always returns `true` by default.
func retry(_ maxAttemptCount: Int = Int.max, delay: DelayOptions, didBecomeReachable: Observable<Void> = Reachability.shared.didBecomeReachable, shouldRetry: @escaping (Error) -> Bool = { _ in true }) -> Observable<Element> {
return retryWhen { (errors: Observable<Error>) in
return errors.enumerated().flatMap { attempt,error -> Observable<Void> in
guard shouldRetry(error),
maxAttemptCount > attempt + 1 else {
return .error(error)
}
let timer = Observable<Int>
.timer(RxTimeInterval.seconds(Int(delay.make(attempt + 1))),
scheduler: MainScheduler.instance)
.map { _ in () }
return Observable.merge(timer, didBecomeReachable)
}
}
}
}
复制代码
对我来讲要作好一份适用性很强的网络架构不是一件很容易的事情,实际上网络请求的复杂度远远不止于此,这里作的仅仅是把一些通用逻辑统一处理,还有不少本文没有讲到。好比
Codable
区分返回的是 Array 仍是 Object 是要不一样处理的这些都是咱们须要去解决的。我当初也踩了无数坑,太难了。 这些问题先留给你们思考吧😆