任務分離:建立可測試的網絡層

做者:Fernando Martín Ortiz 翻譯:BigNerdCoding 若有錯誤歡迎指出。原文連接ios

最近幾年出現了不少設計良好的 iOS 開發架構,這些架構在開發人員中獲得了普遍的傳播和應用。雖然這些架構並非絕對完美的但總的來講這些設計觀念都很是實用,並且這些架構出發點驚人的類似:分離代碼的業務邏輯和界面呈現。接下來我將結束的概念具備萬金油特徵,不管之後以使用何種架構開發,你均可以將其應用其中。git

不錯的常規網絡層

在分享個人觀點以前,首先讓咱們來看看網絡層的常規實現。github

我研究過不少網絡層的代碼,其中的大多數都有 NetworkManagerConnectionManager 或者其餘相似的設計。經過這個單獨的類實現對 APP 中全部網絡請求的管理。雖然這些管理類在代碼可以很好的完成任務,但這個設計卻違背了軟件設計中的核心理念之一:單一職責原則編程

ConnectionManager 中包含了太多的職責和功能,這不能被看做是一個好的編程實踐。並且該類大多數狀況下還會以單例類的形式出現。並非說單例模式必定就有問題,而是單例沒法進行依賴注入,而且也會給測試帶來諸多不便。swift

Networking Layer is commonly implemented as a singleton

上圖的設計實現很是廣泛,甚至在 MVVM、MVP 架構中也存在相似的代碼實踐。網絡

不一樣的方式

數據訪問層代碼實現方式多種多樣,咱們先看看網絡請求的過程:閉包

Steps involved in a network call

如上圖所示,一個完整的網絡請求最少應該有如下三個步驟:架構

  1. 建立網絡請求:包含設置 URL 連接、網絡方法、請求參數、HTTP頭部內容框架

  2. 網絡請求的調度:這是最爲重要的一步。在上一步中咱們設置好了的網絡請求必須在這一步中使用 URLSession 或者其封裝層(例如:Alamofire)進行調度。異步

  3. 解析獲取的數據:將該過程從前面二者中分離出來是很是重要的。在這一步中咱們須要對獲取的 JSON 或者 XML 數據解析爲一個數據實體 Model。

若是你真想建立一個清晰、可測試的的網絡層架構的話,上訴步驟就須要以獨立對象分別進行代碼實現。

Three Step Layer

對象詳解以下:

  1. Request:一個 Request 對象包含了網絡請求中須要配置的全部信息。Request 以類、結構體的形式對單個請求進行配置,一個請求對應一個 Request 對象。

  2. NetworkDispatcher:NetworkDispatcher 負責處理網絡請求並返回響應的對象。另外 NetworkDispatcher 的實現形式是協議。你應該面向該協議進行編碼而不該該是具體的類或者結構體,更不能以單例模式對其進行實現。這樣作的緣由是:咱們能夠應用 mock 測試的方法將其替換成 MockNetworkDispatcher 對象,該對象不執行任何網絡請求而是從一個 JSON 文件獲得響應數據,這樣久很天然的構建了一個可測試的框架。

  3. NetworkTask:NetworkTask 是範型類 Task 的子類。Task 的任務是以同步或者異步的方式從一個輸入類型中獲得一個輸出類型,後面會詳細介紹。你能夠用 RxSwift, ReactiveCocoa, Hydra, Microfutures, FOTask 等類庫來對 Task 進行實現,固然也能夠直接使用閉包。選擇隨你,重要的是設計概念而不是具體的實現細節。

Request 的實現

Request 對象包含了建立 URLRequest 所需的全部配置信息。

下面是代碼實現的示例:

//
//  Request.swift
//
//  Created by Fernando Ortiz on 2/12/17.
//
import Foundation

enum HTTPMethod: String {
    case get, post, put, patch, delete
}

protocol Request {
    var path        : String            { get }
    var method      : HTTPMethod        { get }
    var bodyParams  : [String: Any]?    { get }
    var headers     : [String: String]? { get }
}

extension Request {
    var method      : HTTPMethod        { return .get }
    var bodyParams  : [String: Any]?    { return nil }
    var headers     : [String: String]? { return nil }
}

簡單的不能再簡單了。這段代碼的重點就是將每個請求都做爲獨立對象分離開來。固然你能夠像 Moya 同樣使用枚舉來進行實現,一切都取決於我的的的風格偏心。我我的更喜歡使用面向對象的風格,這樣我能夠實現一個 BaseRequest 類併爲每個具體的請求實現一個子類,如: AuthenticatedRequestGetAllUsersRequestLoginRequest

NetworkDispatcher 的實現

NetworkDispatcher 是一個管理網絡請求的組件。

注意:在文章的中我會使用 RxSwift 最爲示例,可是你徹底能夠作出本身的選擇。

//
//  NetworkDispatcher.swift
//
//  Created by Fernando Ortiz on 2/11/17.
//  Copyright © 2017 Fernando Martín Ortiz. All rights reserved.
//
import Foundation
import RxSwift

protocol NetworkDispatcher {
    func execute(request: Request) -> Observable<Any>
}

NetworkDispatcher 的惟一職責就是執行網絡請求返回響應數據。

這裏使用協議而不是特定實現的緣由是基於協議的實現能夠更容易地進行互換。 您能夠建立一個 MockNetworkDispatcher,它實際上不執行任何「網絡」操做,而是從JSON文件返回一個響應,這讓網絡測試變的更容易。

任務分離

Task 負責單個業務邏輯操做的簡單對象。例如,它多是一些從後臺獲取用戶信息,用戶登陸,用戶註冊相似的業務操做。雖然任務多是同步或者異步的,可是對客戶端而言這些都應該是透明的。這裏我使用了 RxSwift 中的抽象 Observable,固然 Promise, Signal, Future 或者簡單的閉包也能夠實現。

Task 的簡單實現以下:

//
//  Task.swift
//
//  Created by Fernando Ortiz on 2/11/17.
//
import Foundation
import RxSwift

class Task<Input, Output> {
    func perform(_ element: Input) -> Observable<Output> {
        fatalError("This must be implemented in subclasses")
    }
}

這裏我使用了簡單直接的面向對象風格,你也可使用 associated type 這樣的新風格。我之因此選擇面向對象風格是由於它更爲直接也更容易進行代碼實現。

每一個 Task 都須要兩個範型參數:一個 Input 類型,一個 Output 類型。Task 對輸入進行處理並返回數據,該返回數據能夠進行 Observable 形式的抽象封裝。

接下來咱們聲明具體類來進行網絡操做:

//
//  NetworkTask.swift
//
//  Created by Fernando Ortiz on 2/11/17.
//
import Foundation
import RxSwift

class NetworkTask<Input: Request, Output>: Task<Input, Output> {
    let dispatcher: NetworkDispatcher

    init(dispatcher: NetworkDispatcher) {
        self.dispatcher = dispatcher
    }

    override func perform(_ element: Input) -> Observable<Output> {
        fatalError("This must be implemented in subclasses")
    }
}

從上面代碼中咱們能清晰的發現:NetworkTask 中有兩個範型 Input 和 Output,值得注意的是前者必須爲 Request 類型對象。 NetworkTask 實例化時必須傳入 NetworkDispatcher 對象,這樣當咱們想要進行測試的時候,你就能夠很方便的傳入一個MockNetworkDispatcher 對象。

架構回顧

以這種架構方式處理業務邏輯有助於減小系統耦合,在下降的系統複雜性的同時也加強了系統的可測試性。

架構的設計能夠用下圖簡單表示:

Task based network layer

總結

經過多個獨立對象對代碼中的業務邏輯進行分離是軟件設計中好的實踐,它給咱們帶來的更易測試的架構。另外該設計下降了代碼複雜度而且獨立於你使用的其餘架構。該設計能夠應用於 ViewModel, Presenter, Interactor, Store 這樣的架構背後,獲取其餘任何分離業務邏輯和表現邏輯的架構中。

我但願本文可以真正的對你起到幫助。若是你有任何疑問或者更好的方案,請在下方留下你的觀點。

相關文章
相關標籤/搜索