如何设计你的网络请求

概述

几乎全部的项目都须要网络请求,由于他能够给用户呈现更加丰富的内容,方便咱们在不一样设备之间管理同步数据。网络请求会出如今你项目的各个地方:启动页,列表页,登陆注册...因此如何管理组织网络请求是 App 架构中很是重要的一部分。Github 上也有相似的框架好比 Moya, 咱们且称其为网络框架的框架吧。Moya 也有这个框架也发展了好久了,功能很强大,社区也一直很活跃,也有衍生的 RxMoyaReactiveMoya 。但我在使用事后发现他过于 了,一直以为他那种 path + method + parameter 拆开的写法太过于繁琐,那么本文就让咱们来一步步搭建适合本身的网络框架吧。git

提示:为了方便和通用,咱们的网络请求 API 就直接基于 Alamofire 来写好了。github

分析和思路

首先咱们来看看最简单的请求长什么样?编程

AF.request("https://httpbin.org/get").response { response in
    debugPrint(response)
}
复制代码

很是简单对不对?但现实并非这样,在一个请求中咱们须要处理各类疑难杂症,最终一个请求的代码可能会很长很长(长到一个屏幕都放不下!),因此咱们要尽可能抽象和复用这里的逻辑。json

如何下手呢?解决一个问题的最经常使用的方法就是先看清楚问题,而后把大问题拆成小问题,再一个个解决。咱们先来思考下🤔,一个完整请求要作的事情什么:swift

  1. 将 url,method,body 封装成一个 HTTP Request 对象,
  2. 设置请求的 HTTP Header
  3. 接受 HTTP Request 回来的 data 数据
  4. 处理 errorresponse code
  5. 经过 codable 之类的框架将 raw data 转换 model 对象
  6. 请求重试

在常规的业务中,二、三、四、5 每每是能够统一抽象处理的,而最多见作法就是用一个 HTTPClientAPIManager 来统一 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 真香系列😋

不会 RxSwift 建议你们都去学一啦,响应式编程真的很棒棒数据结构

如何支持 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)
      }
    }
  }
}

复制代码

总结

对我来讲要作好一份适用性很强的网络架构不是一件很容易的事情,实际上网络请求的复杂度远远不止于此,这里作的仅仅是把一些通用逻辑统一处理,还有不少本文没有讲到。好比

  1. 如何用单一功能原则(Single responsibility principle)优化这里的逻辑
  2. 当咱们遇到服务端返回的不标准的数据结构怎么处理?
  3. 使用 Codable 区分返回的是 Array 仍是 Object 是要不一样处理的
  4. 当咱们有多个 API 地址好比测试环境和正式环境,那么咱们如何去管理?

这些都是咱们须要去解决的。我当初也踩了无数坑,太难了。 这些问题先留给你们思考吧😆

参考

最后仍是大力推荐下喵神在台湾 iPlayground 的演讲

www.youtube.com/watch?v=Xk4…

相关文章
相关标签/搜索