本文最后更新于 2021年4月4日 晚上
一般 APP 中网络通信层施工从来都不是问题…但遇到过一些项目的网络通信层就问题很多, 要么增删接口复杂, 要么就是使用上问题百出. 这里就来介绍一种利用 RxSwift 实现的网络通信层(仅是利用它能够提供类似异步转”同步”的特性, 其实使用 Promise 也可以达到同样效果).
下面先从使用上入手, 逐步往下深入, 最后完整呈现一个利用 RxSwift 实现网络通信层的方法.
完整实现后的网络通信层的使用 作为网络通信层
的直接用户(也可以说是唯一用户), 业务逻辑层
负责处理上层请求, 并调用网络通信层
接口实现各类通信, 故必须要求网络层有一个友好的, 易用的接口:
1 2 3 4 5 6 7 8 9 10 11 12 import RxSwifttypealias GithubRepoResponse = Observable <[GithubRepositoryEntity ]>class GithubData { static func getDataWithName (_ name : String ) -> GithubRepoResponse { let obv: GithubRepoResponse = APIClient .githubRepositoriesRequest(name: name).make() return obv } }
上述代码中:
基于代码组织管理方面的考虑, 使用 GithubData
类将 Github 相关的请求调用都封装在一起.
getDataWithName
方法的目的是: “发起一个查询对应名字的 github 仓库请求, 将请求结果的可观察序列返回回来”.(其中 APIClient
是一个 enum
, 位于网络通信层
, 它的具体实现之后再讲.)
业务逻辑层中都是以上述简单直接的方式调用网络通信层接口, 即: 提供所需请求参数, 发起请求, 观察结果并进行处理.
网络请求简单工厂: APIClient 利用 APIClient
简单工厂来生产网络请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public enum APIClient { case githubRepositoriesRequest(name: String ) public func make <ResponseEntityType : Codable >() -> Observable <ResponseEntityType > { var request: IAPIRequest switch self { case .githubRepositoriesRequest(let name): request = GithubRepositoryRequest (userName: name) } let resp: Observable <ResponseEntityType > = URLSessionAPIRequester .shared.send(apiRequest: request) return resp } }
接口请求及实现 工厂中的 IAPIRequest
协议表示抽象的 HTTP Request:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protocol IAPIRequest { var method: RequestType { get } var baseURLType: BaseURLType { get } var path: String { get } var query: [String : String ]? { get } var headers: [String : String ]? { get } var httpBody: Data ? { get } }enum RequestType : String { case GET case POST case DELETE case PUT }
在网络通信层中实现了若干种 IAPIRequest
, 比如 github 仓库请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct GithubRepositoryRequest : IAPIRequest { var method: RequestType = .GET var baseURLType: BaseURLType = .github var path: String { return "/users/\(_userName) /repos" } var query: [String : String ]? var headers: [String : String ]? = ["Accept" : "application/vnd.github.v3+json" ] var httpBody: Data ? private let _userName: String init (userName : String ) { _userName = userName } }
要添加一个新的接口请求, 只需要添加一个请求实现类, 然后在工厂中生产出来供外界使用即可(可扩展的关键).
基地址的处理 基地址配置枚举 BaseURLType
:
1 2 3 4 5 6 7 8 9 #if DEBUG enum BaseURLType : String { case github = "https://api.github.com" }#else enum BaseURLType : String { case github = "https://api.github.com" }#endif
核心: 网络请求器实现 对网络请求抽象了一个 IAPIRequester
协议, 比如要使用 URLSession
实现请求发送, 只需要实现一个 URLSessionAPIRequester
即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 protocol IAPIRequester { func send <ResponseEntityType : Codable >(apiRequest : IAPIRequest ) -> Observable <ResponseEntityType > }final class URLSessionAPIRequester : IAPIRequester { static let shared: IAPIRequester = URLSessionAPIRequester () func send <ResponseEntityType : Codable >(apiRequest : IAPIRequest ) -> Observable <ResponseEntityType > { return Observable <ResponseEntityType >.create { observer in let request = apiRequest.request() let task = URLSession .shared.dataTask(with: request) { (data, response, error) in if let error = error { observer.onError(error) return } if let httpResp = response as? HTTPURLResponse , ! (200 ..< 300 ~= httpResp.statusCode) { observer.onError(RequestError .statusCodeError(code: httpResp.statusCode)) return } do { let model: ResponseEntityType = try JSONDecoder ().decode(ResponseEntityType .self , from: data ?? Data ()) observer.onNext(model) } catch let error { observer.onError(error) } observer.onCompleted() } task.resume() return Disposables .create { task.cancel() } } } }public enum RequestError : Error { case statusCodeError(code: Int ) }
这样让网络请求的发送完全隐藏在网络通信层实现中, 如果有特殊需求, 可以提供不同的 IAPIRequester
实现(比如使用 Alamofire 来请求), 在工厂中通过另外的实现来发送请求即可.
为便于使用 URLSession
, 在 IAPIRequest
上扩展了一个方法来获取 URLRequest
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 extension IAPIRequest { func request () -> URLRequest { guard let baseURL = URL (string: baseURLType.rawValue), var components = URLComponents (url: baseURL.appendingPathComponent(path), resolvingAgainstBaseURL: false ) else { fatalError ("Unable to create URL components" ) } components.queryItems = query? .map { param -> URLQueryItem in URLQueryItem (name: param.key, value: param.value) } guard let url = components.url else { fatalError ("Could not get url" ) } var request = URLRequest (url: url) request.httpMethod = method.rawValue headers? .forEach({ request.addValue($1 , forHTTPHeaderField: $0 ) }) request.httpBody = httpBody return request } }
至此, 整个实现就呈现出来了.
如果要添加一个接口, 只需添加一种新的 IAPIRequest
实现类, 然后在工厂生产, 供外界调用即可.