0%

面向协议的网络库业务封装

项目中使用的网络库为 Alamofire ,数据映射库为 ObjectMapper ,由于项目中存在的网络库使用不够友好,维护扩展新的接口比较繁琐,所以抽空重写了一个网络库的封装

基本框架方案的选择

旧的网络库框架使用了单例与扩展的模式,在使用中的形式为 API.requestAAPI.requestA 的形式,随着接口的增多,API 类下的方法日益增多,显得一团乱。所以接口的调用形式改为 Protocol ,哪里使用哪个模块的接口,就遵循对应模块的 API 协议,不再暴露所有API接口在整个项目中。

一般在业务调用接口的时候,希望处理数据的地方也是在当前位置(当然异步的回调放哪里都一样),这样的代码在后续的维护中更好找到业务逻辑,所以回调统一使用闭包,放弃 Target Select 模式。

基础请求 protocol 的创建

我们以最基础的 HTTP 请求举例

1
2
3
4
protocol API {
func request()
}

由于这是总的请求入口,那么需要传入各个请求需要使用的参数,这些又是跟业务相关的东西,那么应该封装在一起,这里传入的参数,只需要能获取到对应需要的各个值,不限定传入的参数类型,那么请求参数也设计成一个 Protocol

然后超时时间,对于这个参数,可能同一个接口在不同位置所需的时间会不一致,所以作为一个保留参数,提供默认值(由于协议的声明不是设置默认值,所以放在 extension 中),现在代码看起来是这样:

1
2
3
protocol API {
func request(config: ApiConfigProtocol, timeoutInterval: TimeInterval)
}

我们还需要集成数据映射,在请求返回数据初步处理之后,那么还需要一个泛型,因为映射的方法也属于一个协议,所以我们需要泛型遵循此协议,请求返回的闭包,参考 Alamofire 的方式,也以返回一个对象的形式来实现串行闭包:

1
2
3
4
5
protocol API {
func request<T: APIMappable>(
config: ApiConfigProtocol,
timeoutInterval: TimeInterval) -> Response<T>
}

现在所有请求需要和返回的要素有了,我们开始构建

构建 APIMappable

这里我们自定义一个 protocol,基于 ObjectMapperBaseMappable 协议

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
protocol APIMappable: BaseMappable {
init?(json: Any?)
}

extension APIMappable {
public init?(map: Map) { self.init(json: nil) }

init?(json: Any?) {
guard let JSON = json as? [String: Any],
let obj: Self = Mapper().map(JSON: JSON) else {
return nil
}

self = obj
}
}

extension Array: APIMappable, BaseMappable where Element: BaseMappable {
public mutating func mapping(map: ObjectMapper.Map) {}

init?(json: Any?) {
if let JSONArray = json as? [[String: Any]] {
let obj: [Element] = Mapper(context: nil).mapArray(JSONArray: JSONArray)
self = obj
} else {
return nil
}
}
}

构建 ApiConfigProtocol

域名与header一般来说比较固定,就直接在extension中返回默认值,它看起来像这样:

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
protocol ApiConfigProtocol {
/// 域名
var domain: String {get}
/// 路径
var path: String {get}
/// 请求方法
var method: HTTPMethod {get}
/// 请求头
var header: HTTPHeaders { get }
/// 请求参数
var parameters: [String: Any]? { get }
/// 参数编码
var encoder: ParameterEncoding {get}
}

extension ApiConfigProtocol {
var domain: String { "https://xxxx.com" }
var header: HTTPHeaders { defaultHeader() }


func defaultHeader() -> HTTPHeaders {
var dic = [String: String]()
dic["token"] = "xxxx"
return HTTPHeaders.init(dic)
}
}

在实际使用中,对应的业务模块实现该协议,例如一个与服务系统相关的模块:

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
42
43
enum APISystemConfig: ApiConfigProtocol {
case sysTime
case sysVersion

var path: String {
switch self {
case .sysTime: return "/sysTime"
case .sysVersion: return "/sysVersion"
}
}

var header: HTTPHeaders {
var defaultHeader = defaultHeader()

switch self {
case .sysTime: return defaultHeader
case .sysVersion:
defaultHeader.add(name: "platform", value: "ios")
return defaultHeader
}
}

var method: HTTPMethod {
switch self {
case .sysTime: return .get
case .sysVersion: return .post
}
}

var parameters: [String : Any]? {
switch self {
case .sysTime: return [:]
case .sysVersion: return ["appVersion": "1.0.0"]
}
}


var encoder: ParameterEncoding {
switch self {
case .sysTime, .sysVersion: return JSONEncoding.default
}
}
}

构建 Response

此类需要管理众多闭包,以便于接口返回后的处理:

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
class Response<T: APIMappable> {
init() {}
weak var request: DataRequest?
func cancel() {
request?.cancel()
}

typealias successBlock = (T) -> Void
typealias errorBlock = (APIError) -> Void


private var successBlocks = [successBlock?]()
private var errorBlocks = [errorBlock?]()


fileprivate func postSuccess(_ model: T) {
successBlocks.forEach {$0?(model)}
}

fileprivate func postError(_ error: APIError) {
errorBlocks.forEach {$0?(error)}
}



@discardableResult
func success(_ completionHandler: successBlock?) -> Self {
modelBlocks.append(completionHandler)
return self
}


@discardableResult
func error(_ completionHandler: errorBlock?) -> Self {
errorBlocks.append(completionHandler)
return self
}
}

搭建Model框架

假如我们的服务器数据为

1
2
3
4
5
6
7
8
{
"code": 200,
"message": "no message",
"data": {
"time": 2942798372937
}

}

data是我们想要的业务数据,codemessage 是用来处理接口的返回逻辑的,我们的 Response Model为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ResponseModel: APIMappable {
var code = 0
var message = ""
var data: Any? = nil


func mapping(map: ObjectMapper.Map) {
code <- map["code"]
message <- map["message"]
data <- map["data"]
}
}

class SysTimeModel: APIMappable {
var time: TimeInterval = 0
func mapping(map: ObjectMapper.Map) {
time <- map["time"]
}
}

实现请求逻辑

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
42
43
44
45
46
47
48
extension API {
func request<T: APIMappable>(
config: ApiConfigProtocol,
timeoutInterval: TimeInterval = 20) -> Response<T> {
let result = Response<T>()

let request = AF.request(config.domain + config.path,
method: config.method,
parameters: config.parameters,
encoding: config.encoder,
headers: config.header
) {$0.timeoutInterval = timeoutInterval}


request.responseData { res in
switch res.result {
case .success(let data):
if let JSON = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments),
let model = ResponseModel(json: JSON) {

switch model.code {
case 200:
if let model = T.init(json: JSON) {
result.postSuccess(model)
} else {
result.postError(APIError(message: "解析错误 \(model.message)"))
}
case 401:
print("token失效")
// loginOut()
default:
result.postError(APIError(message: "未知错误 \(model.message)"))
}

} else {
result.postError(APIError(message: "JSON解析错误"))
}

case .failure(let error):
result.postError(APIError(message: error.localizedDescription))
}
}

result.request = request

return result
}
}

封装业务

1
2
3
4
5
6
7
8
9
10
11
protocol SystemAPI: API {}
extension SystemAPI {
func getSysTime() -> Response<ResponseModel> {
return request(config: APISystemConfig.sysTime)
}

func getSysVersion() -> Response<[ResponseModel]> {
return request(config: APISystemConfig.sysVersion)
}
}

使用

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
extension ViewController: SystemAPI {
func getData() {

getSysTime()
.success { model in
print("model", model.time)
}
.success { model in
print("this model is", model)
}
.error { err in
print("err", err.message)
}


let getSysVersionTask = getSysVersion()


getSysVersionTask.success { list in
list.forEach {print("list model _", $0.time)}
}

getSysVersionTask.error { err in

}

getSysVersionTask.cancel()
}
}