大多数 APP 都须要向服务器请求数据,通常来讲,一个 APP 只须要根据一个后台设计一套网络请求的封装便可。git
可是在开发工做中,可能一个 APP 须要接入其余产线的功能,甚至有可能同一个后台返回的接口也不能适用同一github
个解析规则。当出现这种状况时,MJExtension
、ObjectMapper
、HandyJSON
等模型转换的工具应运而生。json
当咱们使用这些工具时,每每须要有一个肯定的类型,才能完成 data 到 model 的映射。在这个阶段,通常是这swift
样来设计模型:api
class BaseRespose {
var code: Int?
var msg: String?
}
class UserInfo {
var name: String?
var age: Int?
}
class UserInfoResponse: BaseRespose {
var data: UserInfo?
}
复制代码
这样来设计 Network:缓存
network<T: BaseResponse>(api: String, success((data: T) -> ()))
复制代码
在这个阶段,咱们运用泛型约束了模型类。使得任何继承了 BaseResponse
或实现了 BaseResponse
协议的类或结bash
构体能够成功的解析。这样看来,彷佛已经能够作到解析全部的数据结构了,但须要注意的是,此时的 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 的内部对请求结果进行统一的处理。且应该支持类与结构体。
下面让咱们经过一个已经实现的网络请求组件,尝试解决和讨论以上的问题。此组件由如下四部分组成。
.
├── 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、缓存等功能。
实现 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<R>: 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<R: JingDataNetworkDataResponse>: JingDataNetworkDataResponseHandler {
var response: R?
}
复制代码
JingDataNetworkManager
中使用 Moya
和 RxSwift
对网络请求进行了封装,主要作了下面几件事:
// 获取 response
JingDataNetworkManager.base(api: TestApi.m)
.bind(BaseResponseHandler.self)
.single()
.observeOn(MainScheduler.instance)
.subscribe(onSuccess: { (response) in
print(response)
})
.disposed(by: bag)
// 获取 response.data
JingDataNetworkManager.base(api: TestApi.m)
.bind(BaseDataResponseHandler<BaseDataResponse>.self)
.single()
.observeOn(MainScheduler.instance)
.subscribe(onSuccess: { (data) in
print(data.count)
})
.disposed(by: bag)
// 获取 response.listData
JingDataNetworkManager.base(api: TestApi.m)
.bind(BaseListDataResponseHandler<BaseListDataResponse>.self)
.single()
.observeOn(MainScheduler.instance)
.subscribe(onSuccess: { (listData) in
print(listData.count)
})
.disposed(by: bag)
复制代码
除去模型的解析以外,在 Network 的工做中,请求顺序的管理也是一个重头戏。其请求的顺序通常有几种状况。
下面依次来看如何进行实现。
public struct JingDataNetworkSameHandlerSequencer<Handler: JingDataNetworkResponseHandler> {
public init () {}
public func zip(apis: [TargetType], progress: ProgressBlock? = nil, test: Bool = false) -> PrimitiveSequence<SingleTrait, [Handler.Response]> {
var singles = [PrimitiveSequence<SingleTrait, Handler.Response>]()
for api in apis {
let single = JingDataNetworkManager.base(api: api).bind(Handler.self).single(progress: progress, test: test)
singles.append(single)
}
return Single.zip(singles)
}
public func map(apis: [TargetType], progress: ProgressBlock? = nil, test: Bool = false) -> Observable<Handler.Response> {
var singles = [PrimitiveSequence<SingleTrait, Handler.Response>]()
for api in apis {
let single = JingDataNetworkManager.base(api: api).bind(Handler.self).single(progress: progress, test: test)
singles.append(single)
}
return Observable.from(singles).merge()
}
}
复制代码
这里使用了 RxSwift
对请求结果分别进行打包和顺序处理。
使用示例:
let sequencer = JingDataNetworkSequencer.sameHandler(BaseListDataResponseHandler<BaseListDataResponse>.self)
sequencer.zip(apis: [TestApi.m, Test2Api.n])
.subscribe(onSuccess: { (responseList) in
print(responseList.map({$0.listData}))
})
.disposed(by: bag)
sequencer.map(apis: [TestApi.m, Test2Api.n])
.subscribe(onNext: { (response) in
print(response.listData)
})
.disposed(by: bag)
复制代码
不一样的模型相对复杂,由于它意味着不一样的后台或解析规则,同时,顺序请求时,又要求能够获取上一次请求的结果,顺序请求完成时,又能够取得最终的请求结果。
在下面的实现中:
blocks
保存每次请求的代码块,如请求失败时则会打断下一次请求。
semaphore
是信号量,保证本次 block
完成前,下一个 block
会被阻塞。
data
是本次请求的结果,用于传给下一个请求。
public class JingDataNetworkDifferentMapHandlerSequencer {
var blocks = [JingDataNetworkViodCallback]()
let semaphore = DispatchSemaphore(value: 1)
var data: Any?
var bag = DisposeBag()
var requestSuccess = true
var results = [Any]()
var index: Int = 0
public init() {}
@discardableResult
public func next<C: JingDataNetworkResponseHandler, T: TargetType, P>(bind: C.Type, with: @escaping (P) -> T?, progress: ProgressBlock? = nil, success: @escaping (C.Response) -> (), error: ((Error) -> ())? = nil, test: Bool = false) -> JingDataNetworkDifferentMapHandlerSequencer {
let api: () -> T? = {
guard let preData = self.data as? P else { return nil }
return with(preData)
}
return next(bind: bind, api: api, progress: progress, success: success, error: error, test: test)
}
@discardableResult
public func next<C: JingDataNetworkResponseHandler, T: TargetType>(bind: C.Type, api: @escaping () -> T?, progress: ProgressBlock? = nil, success: @escaping (C.Response) -> (), error: ((Error) -> ())? = nil, test: Bool = false) -> JingDataNetworkDifferentMapHandlerSequencer {
let block: JingDataNetworkViodCallback = {
guard let api = api() else {
self.requestSuccess = false
return
}
self.semaphore.wait()
JingDataNetworkManager.base(api: api).bind(C.self)
.single(progress: progress, test: test)
.observeOn(MainScheduler.instance)
.subscribe(onSuccess: { [weak self] (data) in
self?.data = data
self?.results.append(data)
self?.requestSuccess = true
success(data)
self?.semaphore.signal()
}, onError: { [weak self] (e) in
self?.requestSuccess = false
error?(e)
self?.semaphore.signal()
})
.disposed(by: self.bag)
self.semaphore.wait()
// print("xxxxxxxxx")
self.semaphore.signal()
}
blocks.append(block)
return self
}
public func run() -> PrimitiveSequence<SingleTrait, [Any]> {
let ob = Single<[Any]>.create { (single) -> Disposable in
let queue = DispatchQueue(label: "\(JingDataNetworkDifferentMapHandlerSequencer.self)", qos: .default, attributes: .concurrent)
queue.async {
for i in 0 ..< self.blocks.count {
self.index = i
guard self.requestSuccess else {
break
}
self.blocks[i]()
}
if self.requestSuccess {
single(.success(self.results))
}
else {
single(.error(JingDataNetworkError.sequence(.break(index: self.index))))
}
self.requestFinish()
}
return Disposables.create()
}
return ob
}
func requestFinish() {
requestSuccess = true
index = 0
blocks.removeAll()
results.removeAll()
}
deinit {
debugPrint("\(#file) \(#function)")
}
}
复制代码
示例:
let sequencer = JingDataNetworkSequencer.differentHandlerMap
sequencer.next(bind: BaseResponseHandler.self, api: {TestApi.m}, success: { (response) in
print(response)
})
sequencer.next(bind: BaseListDataResponseHandler<BaseListDataResponse>.self, with: { (data: String) -> TestApi? in
print(data)
return .n
}, success: { (response) in
print(response)
})
sequencer.next(bind: BaseListDataResponseHandler<BaseListDataResponse>.self, with: { (data: BaseListDataResponse) -> Test2Api? in
print(data)
return .n
}, success: { (response) in
print(response)
})
sequencer.run().asObservable()
.subscribe(onNext: { (results) in
print(results)
})
.disposed(by: bag)
复制代码
在打包请求中,咱们将一个请求视为一个 task:
public struct JingDataNetworkTask<H: JingDataNetworkResponseHandler>: JingDataNetworkTaskInterface {
public var api: TargetType
public var handler: H.Type
public var progress: ProgressBlock? = nil
public var test: Bool = false
public init(api: TargetType, handler: Handler.Type, progress: ProgressBlock? = nil, test: Bool = false) {
self.api = api
self.handler = handler
self.progress = progress
self.test = test
}
public func single() -> PrimitiveSequence<SingleTrait, H.Response> {
return JingDataNetworkManager.base(api: api).bind(handler).single(progress: progress, test: test)
}
}
复制代码
经过对 Single.zip
的再次封装,完成打包请求的目标:
public struct JingDataNetworkDifferentZipHandlerSequencer {
public init() {}
public func zip<H1: JingDataNetworkResponseHandler, H2: JingDataNetworkResponseHandler, H3: JingDataNetworkResponseHandler>(_ source1: JingDataNetworkTask<H1>, _ source2: JingDataNetworkTask<H2>, _ source3: JingDataNetworkTask<H3>) -> PrimitiveSequence<SingleTrait, (H1.Response, H2.Response, H3.Response)> {
return Single.zip(source1.single(), source2.single(), source3.single())
}
public func zip<H1: JingDataNetworkResponseHandler, H2: JingDataNetworkResponseHandler>(_ source1: JingDataNetworkTask<H1>, _ source2: JingDataNetworkTask<H2>) -> PrimitiveSequence<SingleTrait, (H1.Response, H2.Response)> {
return Single.zip(source1.single(), source2.single())
}
}
复制代码
示例:
let task1 = JingDataNetworkTask(api: TestApi.m, handler: BaseResponseHandler.self)
let task2 = JingDataNetworkTask(api: Test2Api.n, handler: BaseListDataResponseHandler<BaseListDataResponse>.self)
let sequencer = JingDataNetworkSequencer.differentHandlerZip
sequencer.zip(task1, task2).subscribe(onSuccess: { (data1, data2) in
print(data1, data2)
}).disposed(by: bag)
复制代码
至此,关于一个网络请求的组件已经基本完成。而涉及到以下载、上传等功能,已由 Moya
进行实现。
如对你有一些帮助请点一下 star。
其中有一些设计不完善的地方,但愿你们能够提 issue。