本教程在向讀者介紹RxSwift庫以及如何使用Swift編寫響應式的iOS App.react
問題來了, 什麼是RxSwift呢? 這裏有一段介紹:git
RxSwift是一個庫,經過使用可觀察序列和函數式操做符組合異步和基於事件的代碼,容許經過調度程序參數化執行。github
聽起來好像很複雜, 可是請別擔憂, 若是咱們一開始就要去了解編寫響應式的程序, 理解程序背後的概念, 以及查看大量的經常使用術語, 那確定是讓人很頭疼的.web
可是本教程的目標是: 經過解釋若是使用每個API, 而且簡單的介紹它們在iOS app中的實際使用, 而後再慢慢介紹RxSwift的各類API以及Rx的概念.編程
如今咱們將從RxSwift的基本特性開始, 而後逐漸學習中級以及高級的課題, 在學習的過程當中, 咱們會花大量的時間練習新概念, 讓你能夠更加容易的掌握RxSwift, Rx是一個很是普遍的課題, 不可能全部東西都包含在本教程中, 剛開始的目的是讓你對庫有一個堅實的瞭解, 而後你在根據所學的Rx知識再擴展本身得Rx技能.swift
如今, 咱們從一個簡單, 可理解的定義開始, 並在本章節後面討論反應時編程的課題是, 逐步發展到一個更好更寬廣的解釋.數組
RxSwift本質上簡化了異步程序的開發, 它容許代碼對新數據做出反應, 而且以順序的, 隔離的方式處理它.ruby
做爲一個iOS App開發人員, 這樣子的解釋更加通俗易懂, 並且能夠明確的告訴你RxSwift的做用.網絡
即便如今你仍然不清楚細節, 也能夠知道RxSwift就幫助你來編寫異步代碼, 並且咱們都知道要開一個好的, 肯定性的異步代碼是很是困難的, 若是有其餘庫能夠幫助咱們的話, 那確定是很是受歡迎的.閉包
若是咱們試圖用一種簡單, 實際的語言來解釋什麼是異步編程, 可能會獲得如下內容.
iOS App, 在任什麼時候候均可能在作如下的任何事情, 甚至更多:
全部的這些事情幾乎都是在同一時間發生, 當鍵盤隱藏鍵盤的時候, 一直到鍵盤徹底的隱藏, 應用程序中的音頻是不會暫停的, 是吧?
而且程序中的全部不一樣的部分都不會互相阻塞執行, 由於iOS提供了各類的API, 容許咱們在不一樣的線程上執行不一樣的工做, 並在設備不一樣的CPU核心上執行它們.
然而, 當咱們真正去編寫這部分異步代碼的時候是很是困難的, 尤爲是當不一樣的代碼須要處理相同的數據時, 很難想象哪段代碼先更新數據, 或者是哪段代碼讀取最新的值.
蘋果在iOS SDK中提供了不少API來幫助咱們編寫異步代碼, 而且咱們在項目中也已經使用過這些工具庫, 但可能咱們並無考慮過這些工具庫對編寫移動應用程序是如此的重要.
咱們經常使用的工具庫:
因爲大多數典型代碼都是異步執行某些工做的, 並且全部的UI事件本質上都是異步的, 因此不可能假設整個應用程序代碼的執行順序.
畢竟應用程序的代碼根據不一樣的外部因素, 代碼運行的順序可能徹底不一樣, 好比用戶的輸入, 網絡活動或其餘的系統事件(除非有一羣機器人沒日沒夜的在測試咱們的應用程序, 這樣子咱們就能夠方方面面均可以考慮到了)
固然, 並非說編寫好的異步代碼是不可能的, 畢竟上面所列出來蘋果API都是很是先進, 很是專業的, 與其餘平臺相比, 它們的功能都很是強大.
但問題是, 複雜的異步代碼變得很是的難寫, 部分緣由也是由於蘋果的SDK提供了多種API:
若是咱們單獨的使用Delegate, 或者是Closures, NotificationCenter等等, 因爲異步API之間並無共同的語言, 這樣子咱們閱讀和理解代碼並對其執行進行推理都是很是困難的.
爲告終束這個話題並將討論放到更大的話題中, 咱們來看看兩段不一樣的代碼: 一段是同步代碼和一段異步代碼.
爲數組的每一個元素執行操做, 這是咱們在開發中常常都會遇到的, 它是一個很是簡單但很可靠的應用程序邏輯構建塊, 由於它保證了兩件事:
花點時間想一想這意味着什麼, 當咱們迭代一個集合時, 咱們不須要檢查全部元素是否存在, 也不須要回滾以防止另外一個現成在集合的開始插入一個元素.
若是你想在for循環的這些方面玩得更多一些, 能夠在Playground上試試這個:
var array = [1, 2, 3]
for number in array {
print(number)
array = [4, 5, 6]
}
print(array)
複製代碼
數組在for循環中是可變的嗎? 循環遍歷的集合是否會更改? 全部命令的執行順序是怎麼樣的? 若是須要, 能夠修改號碼嗎?
假設每次迭代都是對按鈕點擊的反應, 也就是說用戶每次點擊按鈕時, 應用程序都會打印出數組中的下一個元素:
var array = [1, 2, 3]
var currentIndex = 0
// This method is connected in Interface Builder to a button
@IBAction func printNext(_ sender: Any) {
print(array[currentIndex])
if currentIndex != array.count - 1 {
currentIndex += 1
}
}
複製代碼
請與前面的代碼相同的上下文中考慮這段代碼, 當用戶點擊按鈕時, 會打印出數組的全部元素嗎? 答案是不肯定的.
或者, 另外一段代碼可能會在繼續以後再集合的開頭插入一個新元素?
假設只有printNext(_:)
纔會更改currentIndex, 可是另外一段代碼也可能修改currentIndex, 有多是你同事給你在後面補的刀.
講到這裏, 你可能已經意識到編寫異步代碼的核心問題:
幸虧, 這些都是RxSwift的強項!
接下來, 你將開始瞭解RxSwift是如何工做的, 以及它解決了哪些問題.
RxSwift中的有些術語與異步, 響應式和函數式編程緊密的綁定在一塊兒, 若是你先理解如下的基本術語, 就會更加容易理解.
通常來講, RxSwift試圖解決一下的問題:
狀態是比較難定義的, 要理解狀態, 能夠從一下的實際示例去理解.
當咱們啓動電腦的時候, 它能夠運行的很好, 可是在咱們使用了幾天或者是幾周以後, 它可能會表現得奇怪, 有時候還會耍小脾氣, 忽然不工做了, 可是電腦的硬件和軟件是保持不變的, 一旦咱們重啓了電腦以後, 一樣的硬件和軟件又開始正常工做了.
內存中的數據, 存儲在磁盤的數據, 響應用戶的輸入, 從雲服務獲取數據後留下的全部追蹤, 這些全部的東西都是電腦上的狀態.
管理應用程序的狀態, 特別是在多個異步組件之間共享狀態, 是咱們在本教程中要去學會處理的問題之一.
命令式編程是一種使用語句改變程序狀態的編程規範, 就好像咱們逗狗時使用的命令語言同樣, "去拿XX", "坐下", "躺下"等等, 使用命令代碼告訴應用程序確切的時間和怎麼作.
命令式代碼與計算機可以理解的代碼相似, CPU所作的就是遵循冗長的指令序列, 問題來了, 對於人類來講, 爲複雜的異步應用程序編寫命令式代碼是很是具備挑戰性的, 尤爲是涉及到共享可變狀態時.
例如, 以iOS的ViewController的viewDidAppear(_:)
中的代碼爲例:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setupUI()
connectUIControls()
createDataSource()
listenForChanges()
}
複製代碼
咱們不知道這些方法是作什麼的, 它們是否要更新ViewController自己的屬性? 讓人更加不解的是, 它們的調用順序是否正確, 若是有人無心中交換了這些方法的調用順序, 並將更改的代碼提交了Pull Request, 那麼如今所看到的應用程序行爲可能就會有所不一樣.
既然咱們隊可變狀態和命令式編程有了更多的瞭解, 那麼咱們就能夠將這兩件事的大多數問題歸結爲反作用.
反作用用來表示對代碼當前範圍以外的狀態的任何更改. 例如, 參考上面示例中的最後一段代碼connectUIControls()
可能將某種事情處理程序附加到某些UI組件當中, 這會產生一個反作用, 由於它改變了View的狀態, 應用程序在執行connectUIControls()
以前以一種方式執行, 以後又以另外一種方式執行.
每當修改存儲在磁盤上的數據或者更新屏幕上的UILabel文本時, 都會產生反作用.
反作用自己其實並不算是壞事, 畢竟產生反作用是任何程序的最終目標, 咱們須要在程序執行完成後以某種方式更改世界的狀態.
運行一段時間, 什麼都不作, 會使應用程序變得很是的無用.
咱們要以一種可控的方式去產生反作用, 須要可以肯定哪些代碼片斷會致使反作用, 以及哪些代碼片斷只處理和輸出數據.
RxSwift試圖經過處理如下幾個概念來解決上面列出的問題.
在命令式編程中, 能夠隨意改變狀態, 在函數式編程中, 咱們的目標是最小化引發反作用的代碼. 可是咱們沒有生活在一個完美的世界裏, 那麼平衡就在二者之間. RxSwift結合了命令式代碼和函數式代碼的一些最佳方面.
聲明式代碼容許咱們定義行爲片斷, 只要有相關事件, RxSwift就會運行這些行爲, 而且提供一個不可變的, 獨立的數據塊來處理這些行爲事件.
經過這種方式, 咱們可使用異步代碼, 可是咱們的目標是作出與簡單的for循環相同的假設: 咱們正在處理不可變的數據, 而且能夠以順序說的, 肯定性的方式執行代碼.
反應系統是一個至關抽象的數據, 涵蓋了web或iOS應用程序, 這些應用程序大多都具有如下特性:
如今咱們已經很好的理解了RxSwift能夠幫助咱們解決什麼問題和如何的去解決這些問題, 那麼如今能夠討論Rx的構建快以及它們何如協同工做了.
響應式變成不是一個新概念, 它已經存在了至關長的時間, 但它的核心概念在十年中有了明顯的迴歸.
在此期間, web應用程序變得更加的複雜, 並面臨着管理複雜異步UI的問題, 在服務端, 響應式系統(如上所述)已經成爲了必須.
微軟的一個團隊承擔瞭解決咱們在本章中討論的異步, 可伸縮, 實時應用程序開發問題的挑戰. 他們開發了一個獨立於公司核心團隊的庫, 並在2009年先後提供了一個新的客戶端與服務端的框架, 稱爲.net
的響應式擴展Reactive Extensions for .NET(Rx).
在.NET
的3.5版本中是可選的插件, 後來在4.0版本以後稱爲核心庫, 自2012年以來, 它一直都是一個開源的組件, 開源代碼容許其餘語言和平臺從新實現相同的功能, 這使得Rx成爲擴平臺的標準.
今天有RxJS, RxKotlin, Rx.NET, RxScala, RxSwift等等, 全部這些苦都努力實現基於響應式擴展標準的相同行爲和相同表達性的API, 最終, 使用RxSwift建立iOS應用程序的開發人員也能夠在Web上自由的與使用了RxJS的其餘開發人員討論應用程序的邏輯.
與最初的Rx同樣, RxSwift也能夠處理目前爲止所涉及的全部概念: 處理可變裝, 容許咱們組合事件的序列, 並改進代碼的隔離, 可重用性和解耦等體系結構概念.
讓咱們重溫一下這個定義:
RxSwift找到了傳統命令式Cocoa代碼和純粹函數式代碼之間的最佳平衡點, 它容許咱們經過使用不可變的代碼定義肯定的, 可組合的方式處理異步輸入片斷來響應事件.
咱們能夠經過ReactiveX閱讀關於Rx實現家族的更多信息. 這是關於Rx操做符和核心類的文檔的中心存儲庫. 這也多是你第一個注意到Rx電鰻標誌的地方.
在本教程中, 咱們將涵蓋使用RxSwift開發的基本該你拿, 以及如何在應用程序中使用它們的實際示例.
Rx代碼的三個構建快是Observable, Operator和Schedulers, 下面的小節將詳細介紹這些內容.
Observable<T>
類提供了Rx的基礎代碼: 擁有異步產生事件的能力, 能夠"裝載"不可變類型<T>
的通用數據, 它容許其餘對象訂閱該事件, 或者是values, 而後發出另外一個對象.
Observable<T>
類容許一個或多個觀察者實時響應任何事件, 並更新應用程序的UI, 或者處理和利用新傳入的數據.
ObservableType協議(Observable<T>
符合該協議)很是簡單. 一個可觀察對象只能發射(和觀察者能夠接收)三種類型的事件:
當討論異步事件隨着事件的推移而發出時, 咱們能夠在時間軸上可視化一個Observable的整數序列, 以下所示:
一個Observable能夠發出三個可能事件的簡單契約能夠是Rx中的任何事情, 由於它很是的廣泛, 咱們能夠用它來建立很是複雜的應用邏輯.
因爲Observable契約沒有對Observable對象或者觀察者的本質作出任何的假設, 由於使用事件序列是最終解耦的實踐.
咱們永遠不須要使用Delegate或者Block來容許類與類之間的通訊:
爲了瞭解一些實際狀況, 咱們將研究兩種不一樣的Observable序列: 有限序列和無限序列.
一些Observable的序列發出零個, 一個或者是多個值, 在稍後的時刻, 要麼成功後終止序列, 要麼就發生錯誤後終止序列.
在iOS應用程序中, 考慮從網上下載文件的代碼:
該工做流程準確的描述了經典Observable的生命週期, 代碼以下:
API.download(file: "http://www...")
.subscribe(onNext: { data in
// Append data to temporary file
},
onError: { error in
// Display error to user
},
onCompleted: {
// Use downloaded file
})
複製代碼
API.download(file:)
返回一個Observable的實例, 該實例在網上傳輸數據塊時發出數據.
咱們能夠經過提供onError閉包訂閱錯誤, 在該閉包中, 能夠顯示error.localizedDescription
在UIAlertController中, 或者執行其餘的操做.
最後, 處理一個已完成的事件, 咱們則須要提供一個onCompleted閉包, 在該閉包中, 咱們能夠push一個新的UIViewController來顯示下載的文件, 或者其餘的邏輯處理.
與文件下載活動不一樣, 這些活動要麼天然的終止, 要麼就是強制終止, 有一些序列是無限的, 一般UI事件就是這種無限的Observable序列:
例如, 考慮到咱們須要對應用程序中的改變設備方向時須要做出反應:
這一系列的方向變化並無一個天然的終點, 只要有設備, 就可能有一系列的方向變化, 此外, 有序序列其實是無限的, 因此在開始觀察它時會有一個初始值.
用戶可能歷來沒有旋轉過他們的設備, 但這並不意味着事件序列已經終止了, 它只是沒有發出事件而已.
在RxSwift中, 咱們能夠這樣寫代碼來處理設備的朝向:
UIDevice.rx.orientation
.subscribe(onNext: { current in
switch current {
case .landscape:
// Re-arrange UI for landscape
case .portrait:
// Re-arrange UI for portrait
}
})
複製代碼
UIDevice.rx.orientation
是一個虛構的控件屬性, 它生成一個Observable(這個很簡單, 咱們將在下一章節中學習到). 咱們訂閱它根據當前的方向來更新App UI, 咱們能夠直接跳過onError和onCompleted, 由於這些事件永遠都不會從Observable對象發出.
ObservableType和Observable類實現了大量抽象異步工做的離散部分的方法, 這些方法能夠組合在一塊兒實現複雜的邏輯. 由於它們是高度解耦和可組合的, 因此這些方法一般稱做爲操做符(Operators).
因爲這些操做符主要接受異步的輸入, 而且只產生輸出而不產生反作用, 因此它們能夠很容易的組合在一塊兒, 就像拼拼圖同樣, 而且能夠構建更大的畫面.
例如: 以簡單的數學表達式**(1 + 2) * 3 - 4**爲例:
以一種清晰的, 肯定的方式, 咱們能夠將操做符*****, (), **+和-**按照預約的順序做爲它們輸入的數據塊, 獲取它們輸出的結果並繼續處理表達式, 直接解析完成爲止.
以某種相似的方式, 咱們能夠將Rx操做符應用Observable對象發出的事件, 以肯定的處理輸入和輸出, 知道表達式解析爲最終值, 而後咱們就可使用該值來作其餘的操做:
下面是關於一個觀察方向變化的例子, 調整後咱們就能夠看到一些常見的Rx操做符:
UIDevice.rx.orientation
.filter { value in
return value != .landscape
}
.map { _ in
return "Portrait is the best!"
}
.subscribe(onNext: { string in
showAlert(text: string)
})
複製代碼
每次當UIDevice.rx.orientation
生成.landscape
或.portrait
值, Rx將對發出的數據塊用對應的操做符進行處理.
首先, filter只容許非.landscape
的值經過. 若是設備處於橫向模式, 那麼訂閱的代碼則不會被執行, 由於有filter來抑制這些事件.
在.portrait
值的狀況下, map操做符將獲取方向的類型轉換爲一串衣服穿輸出——文本"Portrait is the best!"
最後, 使用subscribe進行訂閱, 將咱們處理好的字符串, 經過調用一個方法顯示在UIAlertController中.
操做符也是能夠高度組合的——它們老是將數據做爲輸入和輸出的結果, 所以咱們能夠輕鬆地以許多不一樣的方式將它們連接起來, 從而實現比單個操做符多出更多的功能!
在閱讀本教程中, 咱們會慢慢了解到更多複雜的操做符, 它們抽象了更復雜的異步代碼.
線程調度至關於Rx的分派隊列——更加的容易使用.
RxSwift附帶了許多預編譯的調度器, 涵蓋99%的使用場景, 由於RxSwift永遠都不但願須要咱們本身去建立調度器.
事實上, 這個教程裏的前半部分中的大多數示例都很是簡單, 一般都是處理觀察數據和更新UI, 因此在學習了基礎知識以前, 咱們不會去研究調度程序.
例如, 但願能夠指定在SerialDispatchQueueScheduler上觀察下一個事件, 該調度程序使用Grand Central Dispatch在指定隊列串行的運行代碼.
ConcurrentDispatchQueueScheduler將同時運行咱們的代碼, 而OperationQueueScheduler將容許咱們在指定的操做隊列上訂閱調度器.
這裏說一句謝謝RxSwift, 咱們能夠在不一樣的調度器調度相同訂閱的不一樣代碼片斷, 從而獲取最佳的性能.
RxSwift將在訂閱(在下面圖片的左側方)和調度程序(在下面圖片的右側方)充當調度器, 將代碼塊發送到正確的上下文中, 而且容許他們無縫間的處理彼此輸出的數據.
要閱讀此圖, 請按照在不一樣的調度稱重的順序(1, 2, 3, ...)跟蹤彩色的代碼片斷, 例如:
這樣子解釋起來很是的有趣而且很是的方便, 並且咱們也不須要在調度程序上花費太多的精力.
值得一提的是, RxSwift不會以任何的方式改變應用程序的架構, 它主要是處理事件, 異步數據序列和通用通訊.
咱們可使用Rx建立應用程序, 方法能夠是實現Apple developer文檔中定義的MVC(Model-View-Controller)架構, 也能夠選擇實現MVP(Model-View-Presenter)或者是MVVM(Model-View-ViewModel).
若是你想的話, RxSwift對於實現你本身得單向數據流架構也很是有用.
值得注意的是, 咱們絕對不須要重新開始寫一個項目, 而且使它成爲一個響應式應用程序, 能夠慢慢的重構迭代現有項目的各個部分, 或者在爲應用程序構建新功能時簡單的使用RxSwift.
微軟的MVVM體系結構是專門爲在提供數據綁定的平臺上建立事件驅動軟件而開發的, RxSwift和MVVM確實能夠很是好的結合在一塊兒, 在本教程的最後, 咱們會使用RxSwift去實現這種模式.
MVVM和RxSwift之因此可以很好的結合在一塊兒, 是由於ViewModel容許咱們公開Observable屬性, 咱們能夠直接綁定到UIViewController的膠水代碼中的UIKit控件, 這會讓數據模型綁定到UI很是的容易:
本教程全部的其餘示例都會以MVC的架構去實現, 以保證代碼簡單易懂.
RxSwift是通用的, 而且是與平臺無關的Rx規範的實現, 所以它對任何Cocoa或者特定的UIKit的類一無所知.
RxCocoa是RxSwift的夥伴庫, 包含了幫助咱們開發UIKit和Cocoa的類, 除了提供一些高級類以外, RxCocoa還向許多UI組件添加了響應式擴展, 這樣子咱們就能夠訂閱各類UI事件.
例如, 很容易使用RxCocoa訂閱UISwitch的狀態變化, 代碼以下:
toggleSwitch.rx.isOn
.subscribe(onNext: { isOn in
print(isOn ? "It's ON" : "It's OFF")
})
複製代碼
RxCocoa添加了rx, 將isOn屬性(以及其餘屬性)訂閱到UISwitch類, 以即可以將有用的事件訂閱爲響應式Observable序列.
此外, RxCocoa將rx的明明空間添加到了UITextField, URLSession, UIViewController等等, 甚至容許咱們在這個命名空間下定義屬於咱們本身的響應式擴展, 在這個教程的後面咱們會了解到.
因爲RxSwift是開源的, 咱們能夠經過https://github.com/ReactiveX/RxSwift免費得到.
RxSwift是基於MIT協議下發布的, 健兒顏值, 它容許咱們在現有的基礎上將庫使用在免費或者是商業軟件中, 與其餘全部通過麻省理工學院受權的軟件同樣, 版本聲明應該包含在咱們發佈的全部應用程序中.
在RxSwift存儲庫中又不少值得探索的地方, 除了RxSwift和RxCocoa庫以外, 咱們還能夠找到RxTest和RxBlocking, 他們將容許咱們爲RxSwift代碼編寫測試.
除了全部優秀的源代碼(絕對值得咱們一看), 咱們還會找到Rx.playground, 這裏面演示須要交互操做符, 還能夠看看RxExample, 在這個Demo中, 它在演示的過程當中實現了許多的概念.
在項目中包含RxSwift/RxCocoa的最簡單方法是經過CocoaPods和Carthage, 甚至還可使用Swift Package Manager來管理.
本教程使用的是CocoaPods, 哪怕是你之前沒有用過也無所謂, 可是請在看本教程的時候確保使用CocoaPods.
咱們能夠像其餘的pod庫同樣經過CocoaPods安裝RxSwift, 正常的Podfile應該是這樣子的:
use_frameworks!
target 'MyTargetName' do
pod 'RxSwift', '~> 4.4'
pod 'RxCocoa', '~> 4.4'
end
複製代碼
固然, 咱們能夠只導入RxSwift和RxCocoa, 其餘的庫若是要使用, 則能夠在GitHub裏找到.
至於Carthage這裏就不作任何的展現了, 有興趣的朋友能夠自行去尋找資料
RxSwift的項目很是的又活力, 不只僅是由於Rx讓咱們能夠更加簡單的建立應用程序, 還由於圍繞着這個項目所造成的社區.
RxSwift社區很是友好, 開發, 而且熱衷於討論模式, 常見的技術還有互相幫助.
除了官方所提供的RxSwift庫, 咱們還能夠在Community找到更多由Rx愛好者所提供的項目.
更多的Rx庫和實驗就像雨後的蘑菇同樣, 咱們能夠在RxSwiftCommunity找到
若是想認識許多對RxSwift感興趣的人, 也能夠經過Slack專用的頻道: RxSwift for Slack.
這個頻道里有幾千名用戶在相互幫助或討論RxSwift以及其餘配套庫的潛在新特性, 還會有關於RxSwift的博客文章, 會議演講等等.
經過這篇文章, 咱們知道了命令式編程, 函數式編程的一些基本概念, 也初步認識了RxSwift能夠給咱們帶來什麼樣的好處, 能夠更簡單, 更省事的開發咱們想開發的應用程序.
那麼接下來就開始學習RxSwift的基本知識吧.