大多數 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<T: BaseResponse>(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<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
對網路請求進行了封裝,主要做了下面幾件事:
- 網路請求錯誤碼丟擲;
- Data 轉 Response 錯誤丟擲;
- ProgressBlock 設定;
- Test 設定;
- 網路請求 Observer 構造;
使用示例
// 獲取 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 的工作中,請求順序的管理也是一個重頭戲。其請求的順序一般有幾種情況。
- 請求結果以相同模型解析
- 請求回撥依次響應
- 全部請求完畢進行回撥
- 請求結果以不同模型解析
- 請求回撥依次響應
- 全部請求完畢進行回撥
下面依次來看如何進行實現。
相同 Response
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)
複製程式碼
不同 Response
順序請求
不同的模型相對複雜,因為它意味著不同的後臺或解析規則,同時,順序請求時,又要求可以獲取上一次請求的結果,順序請求完成時,又可以取得最終的請求結果。
在下面的實現中:
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。