扬庆の博客

Moya(3) 开发应用篇2

字数统计: 2.3k阅读时长: 10 min
2021/03/26 36 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

Powered By Valine
v1.5.2
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类型的传入, 涵盖了一般使用场景