一般 APP 中网络通信层施工从来都不是问题…但遇到过一些项目的网络通信层就问题很多, 要么增删接口复杂, 要么就是使用上问题百出. 这里就来介绍一种利用 RxSwift 实现的网络通信层(仅是利用它能够提供类似异步转“同步”的特性, 其实使用 Promise 也可以达到同样效果).
下面先从使用上入手, 逐步往下深入, 最后完整呈现一个利用 RxSwift 实现网络通信层的方法.
完整实现后的网络通信层的使用
作为网络通信层的直接用户(也可以说是唯一用户), 业务逻辑层负责处理上层请求, 并调用网络通信层接口实现各类通信, 故必须要求网络层有一个友好的, 易用的接口:
import RxSwift // GithubRepositoryEntity 是响应 JSON 的 Swift 模型 typealias 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 简单工厂来生产网络请求:
public enum APIClient { // 请求 name 对应的用户名下所有 github 仓库列表 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:
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 仓库请求:
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:
#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 即可:
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:
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") } // 携带 queryString components.queryItems = query?.map { param -> URLQueryItem in URLQueryItem(name: param.key, value: param.value) } // 拿到携带请求参数的完整 URL guard let url = components.url else { fatalError("Could not get url") } var request = URLRequest(url: url) // 请求方法 request.httpMethod = method.rawValue // 设置 Headers headers?.forEach({ request.addValue($1, forHTTPHeaderField: $0) }) // 设置请求体 request.httpBody = httpBody return request } }
至此, 整个实现就呈现出来了.
如果要添加一个接口, 只需添加一种新的 IAPIRequest 实现类, 然后在工厂生产, 供外界调用即可.