扬庆の博客

Moya(3) 开发应用篇2

字数统计: 2.3k阅读时长: 10 min
2021/03/26 Share

封装 moya 返回的服务器数据解析

iOS开发中简单界面用一个接口请求可能就达到效果了, 但是对于级联列表,或者表单这类界面, 一般都会有多个接口请求,而且会有 Post Get 上传等多项需求 .

封装 MapModel 方法

让每个网络请求都走这个通道统一返回处理后的json 结果

1
2
3
4
5
6
7
8
9
10
11
12
// Send request
provider.request(.login(userAccount: "18516635543", password: "1234565"))
{ (response) in
switch response {
case .success(let resp):
let baseDataResult = self.mapModel(resp: resp)
// interface use the model
case .failure(_):
print("网络异常, 请检查")
return
}
}

后台返回 json 样例

1
2
3
4
5
6
7
8
{
"errorCode": 0,
"data": {
"name": "yq",
"age": "27"
},
"errorMessage": ""
}

需要考虑到的地方

服务器返回的数据格式

根据公司后台规定去考虑

  1. 后台带有单一数据的 json 例如: 查询微信提交工单结果 ( 只是获取是否通过 \ 被拒绝 )
  2. 后台带有模型数据 : 展示页面填充数据 ( 例如: 微信设置界面 )
  3. 后台带有模型列表数据 ( 例如: 微信的朋友圈数据 列表形式存在 )

根据格式将 BaseData 分出来, baseData 包含 Model 的数据, 或者 ModelList 列表数据

上代码

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
func mapModel(resp: Response) -> MoyaResult<MyServiceModel> {
// 1.过滤状态码
guard let _ = try? resp.filterSuccessfulStatusCodes() else {
return .failure(resp.statusCode, "服务器访问失败")
}

// 下面就是服务器返回的数据的问题了

// 2.解析 json
// 转换成 jsonString
// Setting the NSJSONWritingPrettyPrinted option will generate JSON with whitespace designed to make the output more readable.
// the resulting data is a encoded in UTF-8.
// Setting the NSJSONReadingMutableLeaves option will make the parser generate mutable NSString objects.
guard let jsonObj = try? JSONSerialization.jsonObject(with: resp.data, options: JSONSerialization.ReadingOptions.mutableLeaves), let jsonData = try? JSONSerialization.data(withJSONObject: jsonObj, options: JSONSerialization.WritingOptions.prettyPrinted),
let jsonString = String(data: jsonData, encoding: String.Encoding.utf8) else{
print("数据无效")
return .failure(-1, "数据无效")
}

// 打印 json 字符串
print(jsonString)

// 3.json 转 model
guard let base = JSONDeserializer<BaseData<MyServiceModel>>.deserializeFrom(json: jsonString) else {
return .failure(-1, "数据无效")
}

/*
转换数据格式分为两种:
1. 无数据,只是表示成功或者失败状态
2. 有数据,返回 0.model 1.只有状态码,和描述字符串
*/

// 1. No model data, only status information
if base.errcode == 0 {
// status success ; exam: submit success, download success; no model data to handle.


// valid model data success
if let data = base.data {
return .success(data)
}
return .failure(-1, "数据无效")
}else {
// return valid data, but data is other status
return .failure(base.errcode, base.errmsg)
}
}

看 json 转 model 代码

1
2
3
4
// 3.json 转 model
guard let base = JSONDeserializer<BaseData<MyServiceModel>>.deserializeFrom(json: jsonString) else {
return .failure(-1, "数据无效")
}

这里的 BaseData 就涵盖了后台给的 json 格式

1
2
3
4
5
6
struct BaseData<MyserviceMode>: HandyJSON {
// 根据公司后台返回格式,写一个基础数据模型
var errcode: Int = -1
var errmsg: String = NSLocalizedString("Network_errorCode_null", comment:"")
var data: MyserviceMode? = nil
}

好了, 到这里 我们就拿到后台给我们返回的数据了, 也转成 model, 状态码, 错误信息也有了.

界面使用数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Simulate a network request
let provider = MoyaProvider<MyServiceTarget>.appearance()

// Send request
provider.request(.login(userAccount: "18516635543", password: "1234565")) { (response) in
switch response {
case .success(let resp):
let result = self.mapModel(resp: resp)
switch result {
case .success(let md):
nameLabel.text = md.name
ageLabel.text = md.age
case .failure(_ , let errmsg):
print("后台返回错误信息:\(errmsg)")
}
case .failure(let error):
print("网络异常, u 请检查")
}
}

到这里, 要使用的数据, (后台数据格式规定好了) 那么该考虑, 如何让不同界面也走相同的数据处理呢? 因为数据格式大同小异, 唯一不同的就是返回的 JSON 转模型 的模型不一样;

抽出数据模型进行泛型处理

BaseData 这个数据模型要将 Model 通配出来, 换成泛型的形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

struct BaseData<MyserviceMode>: HandyJSON {
// 根据公司后台返回格式,写一个基础数据模型
var errcode: Int = -1
var errmsg: String = NSLocalizedString("Network_errorCode_null", comment:"")
var data: MyserviceMode? = nil
}

// 自定义一个接收返回值的枚举,处理有数据状态下数据的处理
public enum MoyaResult<MyServiceModel> {
case success(MyServiceModel)
case failure(Int,String)
}

struct MyServiceModel: HandyJSON {
var name: String = ""
var age: String = ""
}

将这里的 MyserviceModel 替换成泛型的形式 -> T, T 的具体形式通过函数外部传进来

先看看转成泛型 T 后的数据样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct BaseData<T>: HandyJSON {
// 根据公司后台返回格式,写一个基础数据模型
var errcode: Int = -1
var errmsg: String = NSLocalizedString("Network_errorCode_null", comment:"")
var data: T? = nil
}

// 自定义一个接收返回值的枚举,处理有数据状态下数据的处理
public enum MoyaResult<T> {
case success(T)
case failure(Int,String)
}

struct MyServiceModel: HandyJSON {
var name: String = ""
var age: String = ""
}

T 的具体类型怎么通过函数传递过来?

回顾上面的代码

  1. 能够看到界面 nameLabel 的值通过self.mapModel(resp: resp) 的返回值去处理的
  2. 界面提示也是从self.mapModel(resp: resp) 的返回拿到的
  3. 所以self.mapModel(resp: resp)的返回值要传到外层界面里使用
1
2
3
4
5
// 自定义一个接收返回值的枚举,处理有数据状态下数据的处理
public enum MoyaResult<T> {
case success(T)
case failure(Int,String)
}

根据上面的 MyServiceModel 替换成了 T 作用是是什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Simulate a network request
let provider = MoyaProvider<MyServiceTarget>.appearance()

// Send request
provider.request(.login(userAccount: "18516635543", password: "1234565")) { (response) in
switch response {
case .success(let resp):
let result = self.mapModel(resp: resp)
switch result {
case .success(let md):
nameLabel.text = md.name
ageLabel.text = md.age
case .failure(_ , let errmsg):
print("后台返回错误信息:\(errmsg)")
}
case .failure(let error):
print("网络异常, u 请检查")
}
}

这里 T 就代表传进来的 Model 类型, 用处就是如上代码, 接口返回直接拿到 MyServiceModel 给界面控件赋值,改成泛型就能继续再向上传递, 通过学习 Moya 的方式, 包装一层 MoyaResult<T> 内部枚举值有 success, failure, 将 T 也就是Model,不同数据二次传递到泛型接口外面, 这样就能达到,不同界面调用这个request 方法, 都能拿到转成不同 Model 返回出来的目的.

上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

struct GenericApi<Target: TargetType> {

let provider = MoyaProvider<Target>.appearance()

func request<ModelT>(target: Target, mapModel: ModelT.Type, completion: @escaping (MoyaResult<ModelT>)->Void ) -> Cancellable {

return provider.request(target) { (result) in
switch result {
case .success(let resp):
completion(resp.mapModel(resp: resp))
case .failure(let moyaErr):
print("无网络请求")
completion(.failure(moyaErr.errorCode, moyaErr.errorDescription!))
}
}
}
}

MoyaResult<ModelT> 这个返回值, 就能不同控制器里面去调用,通过传入不同的 ModelT,就能返回解析成不同 Model的数据 .


回顾到这里, 来看看当前的目录结构

moyaApi 代码结构图


捋一捋这个数据泛型的思维结构

  1. 第一层数据要解析的是 BaseData<MyServiceModel> 换成了 BaseData<T>
  2. 第二层解析的是自定义的 MoyaResult<MyServiceModel> 换成了MoyaResult<T>

T 通过泛型, 包装在方法接口里, 具体模型类通过不同控制器调用传进来, 最后解析后台返回的数据格式, 一层一层的向上传递.

为什么要这样做?

泛型封装的好处就不说了.

其他还有哪些好处?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 这是有 data 里有界面需要的 Model 的 json, 表示成功的数据
{
"errcode" : 0,
"data" : {
"name" : "yangqing",
"age" : "27"
},
"errmsg" : "ok"
}

// 这是没有界面需要的 Model,但也表示成功的数据
{
"errcode" : 0,
"data" : {
"result" : "1" // 这个 result 可能代表了某种状态,例如:提交成功, 注册成功, ... 等等
},
"errmsg" : "ok"
}
  1. 看上面的代码; 如果用户调用的接口只是为了获取结果信息result = 1. 后台的给的数据并没有 T 所包装的模型数据, 这个时候就要在 mapModel的时候要进行特殊处理, 将结果传出来, 还要进行区分界面数据 data, 这个时候就比较烦人;
  2. 现在我们有更优雅的方式, 那就是通过上层传递的泛型可以为 Void 类型,我们一旦知道了这个, 就好在 mapModel 的时候去处理数据; 只要 mapModel 返回了 MoyaResult 的 success, 不返回 data, 我们在回调中也知道了表示该状态为成功数据.

Void类型的传入, 涵盖了一般使用场景

mapModel

1
2
3
4
5
1. 满足 errorCode = 0 

2. Data
2.1 model { "data": {age":"26","name":"yangqing"} }
2.2 result -> {"data":{"result":"1"}} 这种数据通过外部接口传入 ModelType 为 Void

外部通过发行 modelT.Type 传入, 涵盖了 Void 类型. **到此, 封装泛型 Model 接口并返回供不用控制器调用就结束了. **

新的问题: 不同模块(多个 Target) 调用接口如何采用泛型封装多 Target调用数据请求 ??

1. Moya 基本概念

2. Moya 和 Alamofire 关系

3. Moya 开发应用篇 1

4. Moya 开发应用篇 2

5. Moya 开发应用篇 3

CATALOG
  1. 1. 封装 moya 返回的服务器数据解析
  2. 2. 封装 MapModel 方法
  3. 3. 后台返回 json 样例
  4. 4. 需要考虑到的地方
  5. 5. 界面使用数据
  6. 6. 抽出数据模型进行泛型处理
  7. 7. T 的具体类型怎么通过函数传递过来?
  8. 8. 捋一捋这个数据泛型的思维结构
  9. 9. 为什么要这样做?
  10. 10. Void类型的传入, 涵盖了一般使用场景