大多数 APP 都需要向服务器请求数据,一般来说,一个 APP 只需要根据一个后台设计一套网络请求的封装即可。
但是在开发工作中,可能一个 APP 需要接入其他产线的功能,甚至有可能同一个后台返回的接口也不能适用同一个解析规则。当出现这种情况时,MJExtension、ObjectMapper、HandyJSON 等模型转换的工具应运而生。
模型转换
当我们使用这些工具时,往往需要有一个确定的类型,才能完成 data 到 model 的映射。在这个阶段,一般是这
样来设计模型:
class BaseRespose {
var code: Int?
var msg: String?
}
class UserInfo {
var name: String?
var age: Int?
}
class UserInfoResponse: BaseRespose {
var data: UserInfo?
}
这样来设计 Network:
network(api: String, success((data: T) -> ()))
在这个阶段,我们运用泛型约束了模型类。使得任何继承了 BaseResponse 或实现了 BaseResponse 协议的类或结
构体可以成功的解析。这样看来,似乎已经可以做到解析所有的数据结构了,但需要注意的是,此时的 Network
只能处理 BaseRespose,也就意味着这时的 Network 只能处理一种类型。
举例来说,当加入新的接口,且 code 或 msg 的解析规则发生变化时,现在的 Network 就无法使用。
当然,在这个例子中,办法还是有的,比如:
class BaseRespose {}
class UserInfo {
var name: String?
var age: Int?
}
class UserInfoResponse: BaseRespose {
var code: Int?
var msg: String?
var data: UserInfo?
}
BaseRespose 不处理任何解析实现,依靠确定的类型 UserInfoResponse 进行解析,但这样你会发现,无法从
Network 内部获取 code 从而判断请求状态。进行统一的处理,其次,也会产生冗余代码。
而这种情况下,只能是增加 Network 的请求方法,来适应两种不同的结构。
同时,除了增加请求方法之外,你无法使其返回 data、string、json 等数据类型。
其次,在依靠继承关系组成模型的情况下,你也无法使用结构体来进行模型的声明。
因此,一个组件化的 Network,为了适应不同的后台或不同的数据结构,应该具备可以解析任意传入的类型,并
进行输出,同时可以在 Network 的内部对请求结果进行统一的处理。且应该支持类与结构体。
JingDataNetwork
下面让我们通过一个已经实现的网络请求组件,尝试解决和讨论以上的问题。此组件由以下四部分组成。
.
├── JingDataNetworkError.swift
├── JingDataNetworkManager.swift
├── JingDataNetworkResponseHandler.swift
└── JingDataNetworkSequencer.swift
在这个组件中,依赖了以下几个优秀的开源工具,其具体使用不再细表:
## 网络请求
s.dependency 'Moya', '~> 11.0'
## 响应式
s.dependency 'RxSwift', '~> 4.0'
s.dependency 'RxCocoa', '~> 4.0'
如何针对不同后台进行设置
针对每一种后台,或者同一个后台返回的不同结构的响应,我们将其视为一种 Response,通过 JingDataNetworkResponseHandler 来处理一个 Response。
public protocol JingDataNetworkResponseHandler {
associatedtype Response
var response: Response? { set get }
var networkManager: Manager { get }
var plugins: [PluginType] { get }
func makeResponse(_ data: Data) throws -> Response
func makeCustomJingDataNetworkError() -> JingDataNetworkError?
func handleJingDataNetworkError(_ error: JingDataNetworkError)
init()
}
public extension JingDataNetworkResponseHandler {
var networkManager: Manager {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Manager.defaultHTTPHeaders
configuration.timeoutIntervalForRequest = 15
configuration.timeoutIntervalForResource = 60
let manager = Manager(configuration: configuration)
manager.startRequestsImmediately = false
return manager
}
var plugins: [PluginType] {
return []
}
}
每一种 ResponseHandler 要求其具备提供 networkManager,plugins 网络请求基础能力。同时具备完成 Data 到 Response 映射、抛出自定义错误和处理全局错误的能力。
其中 plugins 是 Moya 的插件机制,可以实现 log、缓存等功能。
如何实现 Data 到 Response 的映射
实现 JingDataNetworkResponseHandler 协议让如何完成解析变得相当清晰。
struct BaseResponseHandler: JingDataNetworkResponseHandler {
var response: String?
func makeResponse(_ data: Data) throws -> String {
return String.init(data: data, encoding: .utf8) ?? "unknow"
}
func makeCustomJingDataNetworkError() -> JingDataNetworkError? {
return nil
}
func handleJingDataNetworkError(_ error: JingDataNetworkError) {
}
}
如何实现解析任意的类型
看到这里你可能会有疑惑,Response 每有一个类型都需要重新实现一个 JingDataNetworkResponseHandler 吗?这样会不会太繁琐了?
是这样的。这个问题可以通过对 JingDataNetworkResponseHandler 泛型化进行解决:
struct BaseTypeResponseHandler: JingDataNetworkResponseHandler {
var response: R?
func makeResponse(_ data: Data) throws -> R {
if R.Type.self == String.Type.self {
throw JingDataNetworkError.parser(type: "/(R.Type.self)")
}
else if R.Type.self == Data.Type.self {
throw JingDataNetworkError.parser(type: "/(R.Type.self)")
}
else if R.Type.self == UIImage.Type.self {
throw JingDataNetworkError.parser(type: "/(R.Type.self)")
}
else {
throw JingDataNetworkError.parser(type: "/(R.Type.self)")
}
}
func makeCustomJingDataNetworkError() -> JingDataNetworkError? {
return nil
}
func handleJingDataNetworkError(_ error: JingDataNetworkError) {
}
}
但是大家都清楚,如果一个类或者方法承载了太多的功能,将会变得臃肿,分支条件增加,继而变得逻辑不清,难以维护。因此,适度的抽象,分层,解耦对于中大型项目尤为必要。
而且在这里,Response 还仅仅是基础类型。如果是对象类型的话,那 ResponseHandler 会更加的复杂。因为 UserInfo 和 OrderList 在解析,错误抛出,错误处理等方面可能根本不同。
因此就引出了下面的问题。
如何处理不同类型的错误处理和抛出
为了处理这个问题,我们可以声明一个 JingDataNetworkDataResponse,约束其具有和 JingDataNetworkResponseHandler 相同的能力。
public protocol JingDataNetworkDataResponse {
associatedtype DataSource
var data: DataSource { set get }
func makeCustomJingDataNetworkError() -> JingDataNetworkError?
func handleJingDataNetworkError(_ error: JingDataNetworkError)
init?(_ data: Data)
}
public extension JingDataNetworkDataResponse {
public func makeCustomJingDataNetworkError() -> JingDataNetworkError? {
return nil
}
public func handleJingDataNetworkError(_ error: JingDataNetworkError) {
}
}
public protocol JingDataNetworkDataResponseHandler: JingDataNetworkResponseHandler where Response: JingDataNetworkDataResponse {}
实现这个协议,就会发现 UserInfo 和 OrderList 完全可以使用不同的方式来处理:
struct BaseDataResponse: JingDataNetworkDataResponse {
var data: String = ""
var code: Int = 0
init?(_ data: Data) {
self.data = "str"
self.code = 0
}
func makeCustomJingDataNetworkError() -> JingDataNetworkError? {
switch code {
case 0:
return nil
default:
return JingDataNetworkError.custom(code: code)
}
}
}
struct BaseDataResponseHandler: JingDataNetworkDataResponseHandler {
var response: R?
}
如何发起请求
JingDataNetworkManager 中使用 Moya 和 RxSwift 对网络请求进行了封装,主要做了下面几件事:
网络请求错误码抛出;
Data 转 Response 错误抛出;
ProgressBlock 设定;
Test 设定;
网络请求 Observer 构造;
使用示例
network(api: String, success((data: T) -> ()))
0
时序管理
除去模型的解析之外,在 Network 的工作中,请求顺序的管理也是一个重头戏。其请求的顺序一般有几种情况。
请求结果以相同模型解析
请求回调依次响应
全部请求完毕进行回调
请求结果以不同模型解析
请求回调依次响应
全部请求完毕进行回调
下面依次来看如何进行实现。
相同 Response
network(api: String, success((data: T) -> ()))
1
这里使用了 RxSwift 对请求结果分别进行打包和顺序处理。
使用示例:
network(api: String, success((data: T) -> ()))
2
不同 Response
顺序请求
不同的模型相对复杂,因为它意味着不同的后台或解析规则,同时,顺序请求时,又要求可以获取上一次请求的结果,顺序请求完成时,又可以取得最终的请求结果。
在下面的实现中:
blocks 保存每次请求的代码块,如请求失败时则会打断下一次请求。
semaphore 是信号量,保证本次 block 完成前,下一个 block 会被阻塞。
data 是本次请求的结果,用于传给下一个请求。
network(api: String, success((data: T) -> ()))
3
示例:
network(api: String, success((data: T) -> ()))
4
打包请求
在打包请求中,我们将一个请求视为一个 task:
network(api: String, success((data: T) -> ()))
5
通过对 Single.zip 的再次封装,完成打包请求的目标:
network(api: String, success((data: T) -> ()))
6
示例:
network(api: String, success((data: T) -> ()))
7
项目地址
总结
至此,关于一个网络请求的组件已经基本完成。而涉及到如下载、上传等功能,已由 Moya 进行实现。
如对你有一些帮助请点一下 star。
其中有一些设计不完善的地方,希望大家可以提 issue。
作者:薛定諤
链接:https://juejin.im/post/5b8a7e3251882543010414db
本文由 投稿者 创作,文章地址:https://blog.isoyu.com/archives/tantanruheshejiyige-network-model-zujian.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:10 月 16, 2018 at 05:16 下午