泛型结合 RxSwift 实现网络通信层

本文最后更新于 2021年4月4日 晚上

一般 APP 中网络通信层施工从来都不是问题…但遇到过一些项目的网络通信层就问题很多, 要么增删接口复杂, 要么就是使用上问题百出. 这里就来介绍一种利用 RxSwift 实现的网络通信层(仅是利用它能够提供类似异步转”同步”的特性, 其实使用 Promise 也可以达到同样效果).

下面先从使用上入手, 逐步往下深入, 最后完整呈现一个利用 RxSwift 实现网络通信层的方法.

完整实现后的网络通信层的使用

作为网络通信层的直接用户(也可以说是唯一用户), 业务逻辑层负责处理上层请求, 并调用网络通信层接口实现各类通信, 故必须要求网络层有一个友好的, 易用的接口:

1
2
3
4
5
6
7
8
9
10
11
12
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
}
}

上述代码中:

  1. 基于代码组织管理方面的考虑, 使用 GithubData 类将 Github 相关的请求调用都封装在一起.

  2. getDataWithName 方法的目的是: “发起一个查询对应名字的 github 仓库请求, 将请求结果的可观察序列返回回来”.(其中 APIClient 是一个 enum, 位于网络通信层, 它的具体实现之后再讲.)

业务逻辑层中都是以上述简单直接的方式调用网络通信层接口, 即: 提供所需请求参数, 发起请求, 观察结果并进行处理.

网络请求简单工厂: APIClient

利用 APIClient 简单工厂来生产网络请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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:

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")
}

// 携带 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 实现类, 然后在工厂生产, 供外界调用即可.


泛型结合 RxSwift 实现网络通信层
https://blog.rayy.top/2018/10/07/2019-10-RxSwiftNetworkingLayer/
作者
貘鸣
发布于
2018年10月7日
许可协议