普通的配置方式
假設(shè)我們要做一款展示全球美食圖片和信息的 App。這需要從 API 上拉取數(shù)據(jù),那么,用一個對象來做網(wǎng)絡(luò)請求也就是理所當(dāng)然的了:
struct FoodService { |
一旦我們創(chuàng)建了異步請求,就不能使用 Swift 編程語言(http://www.maiziedu.com/course/16/)內(nèi)建的錯誤處理來同時返回成功響應(yīng)和請求錯誤了。不過,倒是給練習(xí) Result 枚舉創(chuàng)造了機(jī)會(更多關(guān)于 Result 枚舉的信息可以參考 Error Handling in Swift: Might and Magic),下面是一個最基礎(chǔ)的 Result 寫法:
enum Result<T> { |
當(dāng) API 請求成功,回調(diào)便會獲得 Success 狀態(tài)與能正確解析的數(shù)據(jù) —— 在當(dāng)前 FoodService 例子中,成功的狀態(tài)包含著美食信息數(shù)組。如果請求失敗,會返回 Failure 狀態(tài),并包含錯誤信息(如 400)。
FoodService 的 get 方法(發(fā)起 API 請求)通常會在 ViewController 中調(diào)用,ViewController 來決定請求成功失敗后具體的操作邏輯:
// FoodLaLaViewController |
但,這樣處理有個問題…
有什么問題
關(guān)于 ViewController 中 getFood() 方法的問題是:ViewController 太過依賴這個方法了。如果沒有正確的發(fā)起 API 請求或者請求結(jié)果(無論 Success還是 Failure)沒有正確的處理,那么界面上就沒有任何數(shù)據(jù)顯示。
為了確保這個方法沒問題,給它寫測試顯得尤為重要(如果實(shí)習(xí)生或者你自己以后一不小心改了什么,那界面上就啥都顯示不出來了)。是的,View Controller Tests
說實(shí)話,它沒那么麻煩。這有一個黑魔法來配置 View Controller 測試。
OK,現(xiàn)在已經(jīng)準(zhǔn)備好進(jìn)行 View Controller 測試了,下一步要做什么?!
依賴注入
為了正確地測試 ViewController 中 getFood() 方法,我們需要注入 FoodService(依賴),而不是直接調(diào)用這個方法!
// FoodLaLaViewController |
下面的方法便可開始測試:
// FoodLaLaViewControllerTests |
接下來,我們需要對 FoodService 返回值類型進(jìn)行更多的約束。
絕殺 —— 協(xié)議
目前 FoodService 的結(jié)構(gòu)體是這樣:
struct FoodService { |
為了方便測試,我們需要能夠重寫 get 方法,來控制哪個 Result(Success 或 Failure)傳給 ViewController,之后就可以測試 ViewController 是如何處理這兩種結(jié)果。
因?yàn)?span> FoodService 是結(jié)構(gòu)體類型,所以不能對其子類化。但是,你猜怎樣,我們可以使用協(xié)議來達(dá)到重寫目的。
我們可以將功能性代碼單獨(dú)提到一個協(xié)議中:
protocol Gettable { |
注意這里標(biāo)明了引用類型(associated type)。這個協(xié)議將會用在所有的 service 結(jié)構(gòu)體上,現(xiàn)在我們只讓 FoodService 去遵循,但是以后還會有CakeService 或者 DonutService 去遵循。通過使用這個通用性的協(xié)議,就可以在 App 中非常完美的統(tǒng)一所有 service 了。
現(xiàn)在,唯一需要改變的就是 FoodService —— 讓它遵循 Gettable 協(xié)議:
struct FoodService: Gettable { |
這樣寫還有一個好處 —— 良好的可讀性??吹?span> FoodService 時,你會立刻注意到 Gettable 協(xié)議。你也可以創(chuàng)建類似的Creatable,Updatable,Delectable,這樣,service 能做的事情顯而易見!
使用協(xié)議
是時候重構(gòu)一下了!在 ViewController 中,相比之前直接調(diào)用 FoodService 的 getFood 方法,我們現(xiàn)在可以將 Gettable 的引用類型限制為[Food]。
// FoodLaLaViewController |
現(xiàn)在,測試起來容易多了!
測試
要測試 ViewController 的 getFood 方法,我們需要注入遵循 Gettable 并且引用類型為 [Food] 的 service:
// FoodLaLaViewControllerTests |
所以,我們可以注入 Fake_FoodService 來測試 ViewController 的確發(fā)起了請求,并正確的返回了 [Food] 類型的結(jié)果(定義為 [Food] 是因?yàn)?TableView 的 data source 所要用到的類型就是 [Food]):
// FoodLaLaViewControllerTests |
現(xiàn)在你也可以仿照這個寫法完成失敗狀態(tài)的測試(比如,根據(jù)收到的 ErrorType 顯示對應(yīng)的錯誤信息)。
總結(jié)
使用協(xié)議來封裝網(wǎng)絡(luò)層,可以使代碼統(tǒng)一、 可注入、 可測試、更可讀。