[譯] iOS 裏的 MVVM 和 RxSwift

在本文中,我將介紹 iOS 編程中的 MVVM 設計模式以及 RxSwift。本文分爲兩部分,第一部分簡要介紹了設計模式和 RxSwift 的基礎知識,而在 第二部分 裏,有一個實現了 MVVM 和 RxSwift 的示例項目。html

設計模式:


首先,咱們爲何要使用設計模式呢?簡而言之,就是爲了不咱們的代碼亂成一團,固然這不是惟一的緣由,其中有一個緣由是可測試性。設計模式有不少,咱們能夠指出幾個很是受歡迎的模式:MVC、MVVM、MVPVIPER。下面的圖片將這幾個設計模式的分佈協做性,可測試性和易用性進行了比較。前端

Compare of design patterns ( from NSLondon )

這些設計模式都有本身的優缺點,但最終它們都能使咱們的代碼更清晰、簡單而且易於閱讀。本文重點介紹 MVVM,我但願你能在閱讀完 第二部分 後着手實現它。react

讓咱們簡單介紹一下 MVC,而後繼續討論 MVVMandroid


MVC:

蘋果官方建議使用 MVC 進行 iOS 編程,若是你有必定的 iOS 開發經驗,你可能會熟悉 MVC。這個模式由 Model、ViewController 組成,其中 Controller 負責將 Model 鏈接到 View。理論上看起來 View 和 Controller 是兩個不一樣的東西,但在 iOS 的世界中,不幸的是,大多數狀況下它們是一回事。固然,在小型項目中,一切彷佛都符合規律,可是一旦你的項目變得龐大,Controller 因實現了大部分業務邏輯而變得臃腫,這會致使代碼變得混亂,可是若是你能正確編寫 MVC,並儘量地把 Controller 裏的東西解耦,大多數狀況下這個問題將獲得解決。ios

MVC from apple docs
官方文檔中的 MVC

MVVM:

picture from github

MVVM 表明 Model、ViewViewModel,其中,View 和業務邏輯實現了 Controller,View 以及動畫,ViewModel 裏則是 api 的調用。實際上 ViewModel 這層是 Model 和 View 之間的接口而且它給 View 提供數據。有一點要注意的是,若是你在 ViewModel 的文件中看到如下代碼,那你多是在某處犯了一個錯誤:git

import UIKit
複製代碼

這是由於 ViewModel 不該該和 View 有任何牽連,在 第二部分 中咱們將藉助一個例子來研究這篇文章。github

RxSwift:


MVVM 的一個特性是數據和 View 的綁定,而 RxSwift 就很完美地實現了這一點。固然,您也可使用 delegate,KVO 或閉包執行此操做,但 Rx 的有一個特性就是,它是一種思想,在不少語言裏通用,所以它與編程語言關係並不大。你能夠在 這裏 找到它支持的語言列表。如今在這一部分咱們將解釋 RxSwift 的基礎知識,固然,它們也是 Rx 世界的基礎知識。而後在 第二部分 中,咱們將憑藉 MVVM 使用 RxSwift 建立一個項目。編程

響應式編程:

既然 RxSwift 是基於響應式編程的,那這到底是什麼意思呢?swift

在計算機中,響應式編程或反應式編程(Reactive programming)是一種面向數據流和變化傳播的編程範式。這意味着能夠在編程語言中很方便地表達靜態或動態的數據流,而相關的計算模型會自動將變化的值經過數據流進行傳播。—— 維基百科後端

也許你在讀完後對本段的任何內容仍是不怎麼了解,那下面咱們就經過如下的例子來進一步理解它:

假設你如今有三個變量(a,b,c):

var a: Int = 1
var b: Int = 2
var c: Int = a + b // 輸出 3
複製代碼

如今若是咱們將 a 從 1 改成 2 而且咱們打印 c,它的值仍然是 3。可是在響應式編程的世界中一切都變得不同了,c 的值取決於 ab,這意味着若是你把 a 從 1 改成 2,那 c 的值就會自動從 3 變爲 4 而不須要你自行更改。

var a: Int = 1
var b: Int = 2
var c: Int = a + b // 輸出 3
a = 2
print("c=\(c)")
// 輸出 c=3
// 在響應式編程中 c=4
複製代碼

如今讓咱們開始學習 RxSwift 的基礎知識:

在 RxSwift(固然還有其餘 Rx)的世界中,一切事物都是事件流,其中包括 UI 事件和網絡請求等等。請切記這一點,我將用現實生活中的例子來解釋:

你的手機是一個 可觀察對象(Observable),它會產生一些事件,例如鈴聲或者推送通知等,這會讓你引發注意,事實上你訂閱(subscribe)了你的手機,並決定如何處理這些事件,好比你有時候刪除或者查看一些通知,事實上這些事件是一些 信號(signal),而你是作出決定的 觀察者(Observer)

下面讓咱們來代碼來實現它:

Observable 和 Observer(訂閱者):

在 Rx 世界中,一些變量是 Observable,而另外一些是 Observer(或訂閱者)。

所以 Observable 是通用的,若是它確遵循了 ObservableType 協議,你能夠監聽你想要的任何類型。

如今讓咱們定義一些 Observable:

let helloObservableString = Observable.just("Hello Rx World")
let observableInt = Observable.of(0, 1, 2)
let dictSequence = Observable.from([1: "Hello", 2: "World"])
複製代碼

在上面例子中,咱們分別定義了 Observable 類型的 String,Int 和 Dictionary,如今咱們應該 訂閱 咱們的 Observable,這樣咱們就能夠從發出的信號中讀取信息。

helloObservableString.subscribe({ event in
    print(event)
})
// 輸出:
// next(Hello Rx World)
// completed
複製代碼

你可能在想輸出爲何會出現 nextcompleted,爲何 ‘hello world’ 就不能好好打印一個字符串,我得說這就是 Observable 最重要的特性:

實際上每一個 Observable 都是一個 序列,與 Swift 裏 Sequence 的主要區別在於 Observable 的值能夠是異步的。若是你不理解這點並不重要,可是但願你能理解下面的描述,我以圖的方式呈現了這一特性:

sequence of events

在上面的圖中,咱們有三個 Observable,第一行是 Int 類型,從 1 數到 6。在第二行是 String,從 ‘a’ 到 ‘f’,隨即發生了一些錯誤。最後一行是 Observable 類型的手勢,它尚未完成,還在繼續。

這些顯示 Observable 變量事件的圖像叫作大理石圖。想要了解更多信息,您能夠訪問 這個網站 或從 App Store 下載 這個 App(它也是開源的 👍😎,這裏 有 App 的源代碼)。

在 Rx 世界中,對於每一個 Observable,都是由 3 種可能的枚舉值組成:


  1. .next(value: T)

  2. .error(error: Error)

  3. .completed

當 Observable 添加值時,調用 next 並經過相關的 value 屬性(1 到 6,‘a’ 到 ‘f’)將值傳遞給 Observer(或訂閱者)。

若是 Observable 遇到了錯誤❌,則發出錯誤事件而後完成(‘f’ 以後的 X)。

若是 Observable 完成,則調用 completed 事件(6 以後)。

若是你想要取消訂閱一個 Observable,咱們能夠調用 dispose 方法,或者若是你想在你的 View deinit 的時候調用這個方法你應該使用 DisposeBag 在你的類反初始化時來進行你想要的操做。在這裏強調一點,若是你忘記使用 dispose 的話會致使內存泄漏☠️💀。例如,你應該這樣訂閱 Observable:

let disposeBag = DisposeBag()
helloObservableString.subscribe({ event in
    print(event)
}).disposed(by: disposeBag)
複製代碼

如今讓咱們看看將 Rx 與函數式編程相結合有多完美。假設你有 Observable 的 Int 而且你訂閱了它,如今 Observable 會給你一堆 Int,你可使用不少方法改變來自 Observable 的發出信號,例如:


Map:

你可使用 map 方法讓信號在到達訂閱者以前作出一些改變。例如,咱們有 Observable 的 Int,它發出了 2,3,4 三個數字,如今咱們想要它們在傳給訂閱者以前都乘以 10,咱們能夠這麼作:

map marble

Observable<Int>.of(2, 3, 4).map { value in
        return value * 10
    }.subscribe(onNext: {
        print($0)
    }).disposed(by: disposeBag)
// 輸出:20 30 40
複製代碼

Filter:

您可能又會想是否能讓它們在傳給訂閱者以前過濾掉一些值,例如,過濾掉示例中大於 25 的數字:

Observable<Int>.of(2, 3, 4).map { value in
        return value * 10
    }
    .filter( return $0 > 25 )
    .subscribe(onNext: {
        print($0)
    }).disposed(by: disposeBag)
// 輸出:30 40
複製代碼

FlatMap:

又好比您有兩個 Observable 對象,而且您但願將它們合二爲一:

在上面的例子中,Observable A 和 Observable B 被組合在一塊兒並造成一個新的 Observable:

let sequenceA = Observable<Int>.of(1, 2)
let sequenceB = Observable<Int>.of(1, 2)
let sequenceOfAB = Observable.of(sequenceA, sequenceB)
sequenceOfAB.flatMap { return $0 }.subscribe(onNext: {
    print($0)
}).disposed(by: disposeBag)
複製代碼

distinctUntilChanged 或 debounce:

這兩個方法是搜索中最有用的方法之一。例如,用戶想要搜索單詞,您可能在用戶輸入每一個字符時都調用搜索 API。若是用戶快速鍵入,這樣的話你就會進行不少沒必要要的請求。爲了達到此目的,正確的方法應該是在用戶中止鍵入時調用搜索 API。這時您可使用 debounce:

usernameOutlet.rx.text
    .debounce(0.3, scheduler: MainScheduler.instance)
    .subscribe(onNext: { [unowned self] text in
        self?.search(withQuery: text)
    }).addDisposableTo(disposeBag)
複製代碼

在上面的例子中,若是用戶名 TextField 的內容在 0.3 秒內發生變化,則這些信號不會到達訂閱者,所以不會調用搜索方法。只有當用戶在 0.3 秒後中止輸入,訂閱者纔會收到信號並調用搜索方法。

distinctUntilChanged 功能對變化很敏感,這意味着若是兩個信號在信號沒有變化以前獲得相同的信號,它將不會被髮送給用戶。


Rx 世界比你想象的要大得多,我在 文章的第二部分 中講述了我認爲須要的一些基本概念,裏面有一個使用 RxSwift 的實際項目。

來自 raywenderlich 的 RxSwift 入門文章很是棒,我強烈推薦閱讀。

你可能不會在文章中注意到 RxSwift,由於它是 Swift 的高級概念之一,你可能天天都要閱讀不一樣的文章才能弄明白它。您能夠經過 此連接 看到 RxSwift 部分中的幾篇好文章。

你能夠經過 文章的第二部分 將 Rx 引入到 MVVM 的實際項目中,由於經過實例你將更好、更容易地理解 RxSwift 的概念。

你能夠經過 Twitter 或者發送 email 來聯繫到本文做者,郵箱是 mohammad_z74@icloud.com ✌️

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索