1. 程式人生 > >用 RxSwift + Moya 寫出優雅的網路請求程式碼

用 RxSwift + Moya 寫出優雅的網路請求程式碼

RxSwift

Rx 是微軟出品的一個 Funtional Reactive Programming 框架,RxSwift 是它的一個 Swift 版本的實現。
RxSwift 的主要目的是能簡單的處理多個非同步操作的組合,和事件/資料流。
利用 RxSwift,我們可以把本來要分散寫到各處的程式碼,通過方法鏈式呼叫來組合起來,非常的好看優雅。

舉個例子,有如下操作:
點選按鈕 -> 傳送網路請求 -> 對返回的資料進行某種格式處理 -> 顯示在一個 UILabel 上

程式碼如下:

Swift
1 2 3 4 5 6 7 sendRequestButton .rx_tap .flatMap(viewModel.loadData) .throttle(0.3,scheduler:MainScheduler.instance) .map{"\($0.debugDescription)"} .bindTo(self.resultLabel.rx_text) .addDisposableTo(disposeBag)

是不是看上去很優雅呢?

另外這篇文章中也有一個類似的例子:

112564398-fbac92b6b8684c36

對應的程式碼是:

Swift
1 2 3 4 5 6 button .rx_tap// 點選登入 .flatMap(provider.login)// 登入請求 .map(saveToken)// 儲存 token .flatMap(provider.requestInfo)// 獲取使用者資訊 .subscribe(handleResult)// 處理結果

用一連串的鏈式呼叫就把一系列事件處理了,是不是很不錯。

Moya

Moya 是 Artsy 團隊的 Ash Furrow 主導開發的一個網路抽象層庫。它在 Alamofire 基礎上提供了一系列簡單的抽象介面,讓客戶端程式碼不用去直接呼叫 Alamofire,也不用去關心 NSURLSession。同時提供了很多實用的功能。
它的 Target -> Endpoint -> Request

模式也使得每個請求都可以自由定製。

下面進入正題:

建立一個請求

Moya 的 TargetType 協議規定的建立網路請求的方法,用列舉來建立,很有 Swift 的風格。

Swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 enumDataAPI{ caseData } extensionDataAPI: TargetType{ varbaseURL: NSURL{returnNSURL(string:"http://localhost:3000")!} varpath: String{ return"/data" } varmethod:Moya.Method{ return.GET } varparameters:[String:AnyObject]?{ returnnil } varsampleData: NSData{ returnstubbedResponseFromJSONFile("stub_data") } varmultipartBody:[Moya.MultipartFormData]?{ returnnil } }

建立資料模型

資料模型的建立用了 SwiftyJSONMoya_SwiftyJSONMapper,方便將 JSON 直接對映成 Model 物件。

Swift
1 2 3 4 5 6 7 8 9 10 structDataModel: ALSwiftyJSONAble{ vartitle:String? varcontent:String? init?(jsonData:JSON){ self.title=jsonData["title"].string self.content=jsonData["content"].string } }

傳送請求

我們可使用 Moya 自帶一個 RxSwift 的擴充套件來發送請求。

Swift
1 2 3 4 5 6 7 8 9 10 11 classViewModel{ private letprovider=RxMoyaProvider()// 建立為 RxSwift 擴充套件的 MoyaProvider funcloadData()->Observable{ returnprovider .request(.DataRequest)// 通過某個 Target 來指定傳送哪個請求 .debug()// 列印請求傳送中的除錯資訊 .mapObject(DataModel)// 請求的結果對映為 DataModel 物件 } }

然後在 ViewController 中就可以寫上面說到過的那一段了

Swift
1 2 3 4 5 6 sendRequestButton .rx_tap// 觀察按鈕點選訊號 .flatMap(viewModel.loadData)// 呼叫 loadData .map{"\($0.title) \($0.content)"}// 格式化顯示內容 .bindTo(self.resultLabel.rx_text)// 繫結到 UILabel 上 .addDisposableTo(disposeBag)// 新增到 disposeBag,當 disposeBag 釋放時,這個繫結關係也會被釋放

這樣就實現了 點選按鈕 -> 傳送網路請求 -> 顯示結果
上面這一段沒有考慮錯誤處理,這個後面會說。

URL 快取

URL 快取則是採用 Alamofire 的快取處理方式——用系統快取(NSURLCache)。
NSURLCache 預設採用的快取策略是 NSURLRequestUseProtocolCachePolicy
快取的具體方式可以由服務端在返回的響應頭部新增 Cache-Control 欄位來控制。

離線快取

有一種快取是系統的快取做不到的,就是離線快取。
離線快取的流程是:
發請求前先看看本地有沒有離線快取
有 -> 使用離線快取資料渲染介面 -> 發出網路請求 -> 用請求到的資料更新介面
無 -> 發出網路請求 -> 用請求到的資料更新介面

由於 Moya 沒有提供離線快取這個功能,只能自己寫了。
為 RxMoyaProvider 擴充套件離線快取功能:

Swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 extensionRxMoyaProvider { functryUseOfflineCacheThenRequest(token:Target)->Observable{ returnObservable.create{[weakself]observer->Disposable in letkey=token.cacheKey// 快取 Key,可以根據自己的需求來寫,這裡採用的是 BaseURL + Path + Parameter轉化為JSON字串 // 先讀取快取內容,有則發出一個訊號(onNext),沒有則跳過 ifletresponse=HSURLCache.sharedInstance.cachedResponseForKey(key){ observer.onNext(response) } // 發出真正的網路請求 letcancelableToken =self?.request(token){resultin switchresult{ caselet.Success(response): observer.onNext(response) observer.onCompleted() HSURLCache.sharedInstance.cacheResponse(response,forKey:key) caselet.Failure(error): observer.onError(error) } } returnAnonymousDisposable { cancelableToken?.cancel() } } } }

以上程式碼建立了一個訊號序列,當有離線快取時,會發出一個訊號,當網路請求結果返回時,會發出一個訊號,當網路請求失敗時,也會發出一個錯誤訊號。

1 2 3 上面的HSURLCache是我自己寫的一個快取類,通過SQLiteMoyaResponse物件儲存到資料庫中。 由於MoyaResponse物件是被`final`修飾的,無法通過繼承方式為其新增NSCoder實現。所以就將Response的三個屬性分別儲存。 讀快取資料時也是讀出三個屬性的資料,再用他們建立成Response物件。
Swift
1 2 3 4 5 6 7 funcloadData()->Observable{ returnprovider .tryUseOfflineCacheThenRequest(.DataRequest) .debug() .distinctUntilChanged() .mapObject(DataModel) }

使用離線快取的網路請求方式可以寫成這樣,呼叫了上面所說的 tryUseOfflineCacheThenRequest 方法。
並且這裡用了 RxSwift 的 distinctUntilChanged 方法,當兩個訊號完全一樣時,會過濾掉後面的訊號。這樣避免頁面在資料相同的情況下渲染兩次。

錯誤處理

可以通過判斷 event 物件來處理錯誤,程式碼如下:

Swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 sendRequestButton .rx_tap .flatMap(viewModel.loadData) .throttle(0.3,scheduler:MainScheduler.instance) .map{"\($0.title) \($0.content)"} .subscribe{eventin switchevent{ case.Next(letdata): print(data) case.Error(leterror): print(error) case.Completed: break } } .addDisposableTo(disposeBag)

本地假資料

這時 Moya 的一個功能,可以在本地放置一個 json 檔案,網路請求可以設定成讀取本地檔案內容來返回資料。可以在介面故障或為開發完時,客戶端可以先用假資料來開發,先走通流程。

只要在建立 RxMoyaProvider 時指定一個引數 stubClosure

使用本地假資料:

Swift
1 RxMoyaProvider(stubClosure:MoyaProvider.ImmediatelyStub)

使用網路介面真實資料:

Swift
1 RxMoyaProvider(stubClosure:MoyaProvider.NeverStub)

Moya 也提供了一個模擬網路延遲的方法。
使用本地假資料並有 3 秒的延遲:

Swift
1 RxMoyaProvider(stubClosure:MoyaProvider.DelayedStub(3))

Header 處理

例如如果想要在 Header 中新增一些欄位,例如 access-token,可以通過 Moya 的 Endpoint Closure 方式實現,程式碼如下:

Swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 letcommonEndpointClosure={(target:Target)->Endpoint in varURL=target.baseURL.URLByAppendingPathComponent(target.path).absoluteString letendpoint=Endpoint(URL:URL, sampleResponseClosure:{.NetworkResponse(200,target.sampleData)}, method:target.method, parameters:target.parameters) // 新增 AccessToken ifletaccessToken=currentUser.accessToken{ returnendpoint.endpointByAddingHTTPHeaderFields(["access-token":accessToken]) }else{ returnendpoint } }

外掛機制

另外 Moya 的外掛機制也很好用,提供了兩個介面,willSendRequestdidReceiveResponse,可以在請求發出前和請求收到後做一些額外的處理,並且不和主功能耦合。

Moya 本身提供了列印網路請求日誌的外掛和 NetworkActivityIndicator 的外掛。

例如檢測 access-token 的合法性:

Swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

相關推薦

RxSwift + Moya 優雅網路請求程式碼

RxSwift Rx 是微軟出品的一個 Funtional Reactive Programming 框架,RxSwift 是它的一個 Swift 版本的實現。 RxSwift 的主要目的是能簡單的處理多個非同步操作的組合,和事件/資料流。 利用 RxSwift,我們可以

優雅的js程式碼

一.常量相關 1.定義常量 var a=1; // 錯誤:"var"定義的"常量"是可變的,在宣告一個常量時,該常量在整個程式中都應該是不可變的。 正解: const a=1 2.給常量賦值 let lastName = fullName[1]; // 錯誤:如果fullName=[],那麼fullN

如何優雅的java程式碼

一、不要使用魔法數字,儘量定義列舉、常量、巨集: 我常常見到表示各種狀態的數字,0,1,2....,我真的不知道這表示什麼含義,如果 你在不在文件中說明的話,這個東東過幾天連你自己都不知道個一二三了。 二、命名要具有描述力,儘量使用全名而不是自創的縮寫,除非地球人都這麼用這

人人都能PythonLSTM-RNN的程式碼![你的神經網路學習最佳起步]

0. 前言 本文翻譯自部落格: iamtrask.github.io ,這次翻譯已經獲得trask本人的同意與支援,在此特別感謝trask。本文屬於作者一邊學習一邊翻譯的作品,所以在用詞、理論方面難免會出現很多錯誤,假如您發現錯誤或者不合適的地方,可以給我留言,謝謝! -

程式設計師如何優雅程式碼

一直以來,關於“程式碼規範”的話題都備受關注,業界甚至有很多流傳甚廣的段子不斷調侃之。既然程式碼規範能引起這麼大的共鳴,那麼今天我們談談一個程式設計師的自我修養——如何寫出優雅的程式碼? Martin(Bob大叔)曾在《程式碼整潔之道》一書中說:當你的程式碼在做 Code Review 時,審查

幾個簡單的技巧讓你的vue.js程式碼優雅

本文參考自油管上某個國外大神的公開演講視訊,學習了一下覺得很不錯,所以在專案中也使用了這些不錯的技巧。 1. watch 與 computed 的巧妙結合 如上圖,一個簡單的列表頁面。 你可能會這麼做: created(){ this.fetchData() }, watch: { keyword(

React 系列 - 優雅的路由

前言 自前端框架風靡以來,路由一詞在前端的熱度與日俱增,他是幾乎所有前端框架的核心功能點。不同於後端,前端的路由往往需要表達更多的業務功能,例如與選單耦合、與標題耦合、與“麵包屑”耦合等等,因此很少有拆箱即用的完整方案,多多少少得二次加工一下。 1. UmiJS 簡述 優秀的框架可以縮短 90% 以上

原生javascriptjquery中slideUp和slideDown效果

設定塊級元素的CSS屬性overflow為hidden,然後動態改變height即可 var header=document.getElementsByTagName('header')[0]; header.style.transition='height 500ms'; header.style

Android妙SPDY協議提高移動端網路請求效能

    本文旨在提出一種提高移動端網路效能的可行方案。我們知道目前移動端使用的網路請求協議基本上都是http。用的最多的是http/1.1,http/2.0正在逐漸壯大,實際上http/2.0是基於google提出的SPDY協議改進而來。廢話不多說,馬上進入正題

iOS cell高度自適應 - 教你優雅的table view

作為一個iOS開發者,自然少不了了table view打交道,table view中最令人頭疼的是各種cell的高度計算了,雖然技術上並不難,但是對於自定義cell來說一旦控制元件比較多,計算起來就會很麻煩,會出現很多和height相關的程式碼,萬一稍有偏差,就

人人都能PythonLSTM-RNN的程式碼

0. 前言 本文翻譯自部落格: iamtrask.github.io ,這次翻譯已經獲得trask本人的同意與支援,在此特別感謝trask。本文屬於作者一邊學習一邊翻譯的作品,所以在用詞、理論方面難免會出現很多錯誤,假如您發現錯誤或者不合適的地方,可以給我留言,謝謝! 1. 概要 我的最佳學習法就是通過玩

PythonLSTM-RNN的程式碼

0. 前言 本文翻譯自部落格: iamtrask.github.io ,這次翻譯已經獲得trask本人的同意與支援,在此特別感謝trask。本文屬於作者一邊學習一邊翻譯的作品,所以在用詞、理論方面難免會出現很多錯誤,假如您發現錯誤或者不合適的地方,可以給我留言,謝謝!

java程式設計-如何優雅程式碼

1. java判斷null!=a和a!=null的區別? 正常來說沒有區別,我們想判斷a是否為null, 可以寫if(a==null), 但是這有個問題,一不小心手滑了就寫成if(a=null). 在不少語言裡這是可以編譯&執行的,並且得不到你希望的結

socket 通訊客戶端和伺服器端的通訊 , 要求客戶傳送資料後能夠回顯相同的資料

比較基礎的一個服務端客戶端互相通訊的程式/** * @author john socket 的伺服器端,接收到資料後列印到控制檯 的資料 * */ public class ServiceSocket_1 { public static void main(

如何根據例圖例描述

如何根據用例圖寫出用例描述 前言:因為用例描述中的執行者和用例名很容易通過用例圖得出來,所以下面講的主要內容是如何通過用例圖獲得用例描述中的互動動作序列。 第一步 用例分類 A.用例分類是什麼???   用例分類是指把有關係(包含或擴充套件)的用例放到一起,與其他用例無關係的用例單獨成排。

Guava - 拯救垃圾程式碼優雅高效,效率提升N倍

![Google Guava](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20201022013126998.png) > 最近在看一個同學程式碼的時候,發現程式碼中大量使用了 Google 開源的 Guava 核心庫中的內容,讓

一段Python程式碼實現刪除一個list裡面的重複元素?

  方法1:使用set函式  s=set(list),然後再list(s)   方法2:append      1 def delList(L): 2 L1 = [] 3 for i in L: 4

如何才能沒有BUG的程式碼?

1947年9月9日,美國海軍准將 Grace Hopper 在哈佛學院計算機實驗室裡使用 Mark II 和 Mark III 計算機進行研究工作。她的團隊跟蹤到 Mark II 上的一個錯誤,操作人員發現是由於一隻飛蛾鑽到了 Mark II 的繼電器裡導致的。團隊清除了這隻

關於單元測試,如何可測試的程式碼

    單元測試在一個完整的軟體開發流程中是必不可少的、非常重要的一個環節。通常寫單元測試並不難,但有的時候,有的程式碼和功能難以測試,導致寫起測試來困難重重。因此,寫出良好的可測試的(testable)程式碼是非常重要的。接下來,我們簡要地討論一下什麼樣的程式碼是難以測試的,我們應該如何避免寫出難以測試的程

怎樣高效能的 Java 程式碼

在這篇文章中,我們將討論幾個有助於提升Java應用程式效能的方法。我們首先將介紹如何定義可度量的效能指標,然後看看有哪些工具可以用來度量和監控應用程式效能,以及確定性能瓶頸。 我們還將看到一些常見的Java程式碼優化方法以及最佳編碼實踐。最後,我們將看看用於提升Java應用程式效能的JVM調優技巧和架構調整