談談如何設計一個 Network->Model 組件

大多數 APP 都須要向服務器請求數據,通常來講,一個 APP 只須要根據一個後臺設計一套網絡請求的封裝便可。git

可是在開發工做中,可能一個 APP 須要接入其餘產線的功能,甚至有可能同一個後臺返回的接口也不能適用同一github

個解析規則。當出現這種狀況時,MJExtensionObjectMapperHandyJSON 等模型轉換的工具應運而生。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 只能處理一種類型。網絡

舉例來講,當加入新的接口,且 codemsg 的解析規則發生變化時,如今的 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 要求其具有提供 networkManagerplugins 網絡請求基礎能力。同時具有完成 DataResponse 映射、拋出自定義錯誤和處理全局錯誤的能力。

其中 pluginsMoya 的插件機制,能夠實現 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 會更加的複雜。由於 UserInfoOrderList 在解析,錯誤拋出,錯誤處理等方面可能根本不一樣。

所以就引出了下面的問題。

如何處理不一樣類型的錯誤處理和拋出

爲了處理這個問題,咱們能夠聲明一個 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 {}
複製代碼

實現這個協議,就會發現 UserInfoOrderList 徹底可使用不一樣的方式來處理:

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 中使用 MoyaRxSwift 對網絡請求進行了封裝,主要作了下面幾件事:

  • 網絡請求錯誤碼拋出;
  • 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)
複製代碼

項目地址

github.com/tianziyao/J…

總結

至此,關於一個網絡請求的組件已經基本完成。而涉及到以下載、上傳等功能,已由 Moya 進行實現。

如對你有一些幫助請點一下 star。

其中有一些設計不完善的地方,但願你們能夠提 issue。

相關文章
相關標籤/搜索