學習RxSwift & MVVM-C(二)

RxCocoa

RxCocoa已經將UIKit中很多視圖控件的很多屬性值封裝成了 Observable
RxSwift是基礎,它工作于各種類型的Swift,但是并不能指定用戶交互、網絡請求,但是RxCocoa就可以幫助我們做這些事情。RxCocoa是一個獨立的庫,允許我們使用許多預置的特性,這樣能夠更好的與UIKit和Cocoa進行整合。RxCocoa能夠讓我們進行響應式網絡,響應式的用戶交互和綁定數據模型到UI控件。大多數的UIKit控件都有響應式擴展,都是通過rx熟悉進行使用。

RxCocoa能夠工作在多平臺,iOS (iPhone, iPad, Apple Watch), Apple TV ,macOS。每個平臺都有一系列自定義的封裝,提供了許多UI控件的擴展和一些SDK類。比如:NSButton+Rx.swift

cancelButton.rx.tap
    .bind(to: viewModel.cancel)
    .disposed(by: disposeBag)

比如:按鈕的點擊,button.rx.tap 是 ControlEvent 類型,ControlEvent 實現了 ControlEventType 協議,而 ControlEventType 協議繼承自 ObservableType,因此可以把 ControlEvent 理解為一種特殊的 Observable,它可以使用.asObservable()方法轉換為 Observable。

UITextField+Rx.swift 中

public var text: ControlProperty<String?> {
        return value
    }
    
    /// Reactive wrapper for `text` property.
    public var value: ControlProperty<String?> {
        return base.rx.controlPropertyWithDefaultEvents(
            getter: { textField in
                textField.text
            },
            setter: { textField, value in
                // This check is important because setting text value always clears control state
                // including marked text selection which is imporant for proper input 
                // when IME input method is used.
                if textField.text != value {
                    textField.text = value
                }
            }
        )
    }

textfield.rx.text 屬性, 是 ControlProperty 類型,ControlProperty 實現了 ControlPropertyType 協議,而 ControlPropertyType 協議繼承自 ObservableType, ObserverType,因此可以把 ControlEvent 理解為一種特殊的 Subject,能夠被訂閱而且能夠有新值的注入。

而 UILabel+Rx.swift

extension Reactive where Base: UILabel {
    
    /// Bindable sink for `text` property.
    public var text: Binder<String?> {
        return Binder(self.base) { label, text in
            label.text = text
        }
    }

    /// Bindable sink for `attributedText` property.
    public var attributedText: Binder<NSAttributedString?> {
        return Binder(self.base) { label, text in
            label.attributedText = text
        }
    }
    
}

label.rx.text 屬性, 是 Binder 類型,而 Binder 實現了 ObserverType,因此可以把 Binder 理解為一種特殊的 Observer,它可以使用 .asObserver() 方法轉換為 Observer。
可以看出 text 的實現不同,TextField 的值既可以監聽,又可以被監聽,而 label 的 text 的值只能監聽。

在RxCocoa中綁定是單向的,只能由被觀察者到觀察者,不能反過來操作。執行綁定操作的基礎就是函數bind(to:)。為了綁定一個觀察者序列(observable)到其它實體(實體就是subject,能夠處理值也可以寫值。subject是非常的重要,在Cocoa中。因為像UILabel, UITextField, and UIImageView都是可變數據,能夠被設置值和獲取值),接受者(receiver)必須遵守觀察者協議(ObserverType)。但是bind(to:)不僅僅是用于綁定用戶界面和潛在的數據,也可以用于其它目的,例如:你能夠使用bind(to:)創建一個獨立的過程(processes),以致于觀察者序列(observable)能夠觸發一個subject并且執行一些后臺任務而并不需要顯示任何內容在屏幕上??偠灾?,bind(to:)就是特殊的 subscribe()版本。

常見控件用法

UITextField

textField.rx.text.orEmpty.changed.subscribe(onNext: {
    print("輸出內容: \($0)")
}).disposed(by: disposeBag)

如果同時監聽多個 textfield

Observable.combineLatest(inputField1.rx.text.orEmpty, inputField2.rx.text.orEmpty) {
    (textValue1, textValue2) in
        print("你輸入的號碼是: \(textValue1)-\(textValue2)")
}

UILabel

inputField.rx.text
    .bind(to: label.rx.text)
    .disposed(by: disposeBag)

UISlider

slider.rx.value.subscribe(onNext: {
    print("當前slider值為: \($0)")
}).disposed(by: disposeBag)

UISegmentedControl

let segmentControl = UISegmentedControl()
// ...
segmentControl.rx.selectedSegmentIndex.subscribe(onNext: {
    print("當前頁: \($0)")
}).disposed(by: disposeBag)

UISwitch

switchButton.rx.isOn.bind(to: button.rx.isEnabled)
    .disposed(by: disposeBag)

UITableView

items.bind(to: tableView.rx.items) { tableView, row, element in
    let cell = tableView.dequeueReusableCell(withIdentifier: "cellid")!
    cell.textLabel?.text = "\(row): \(element)"
    return cell
}.disposed(by: disposeBag)

tableView.rx.itemSelected.subscribe(onNext: { indexPath in
    print("選中項的indexPath為: \(indexPath)")
}).disposed(by: disposeBag)

tableView.rx.modelSelected(String.self).subscribe(onNext: { item in
    print("選中項的標題為: \(item)")
}).disposed(by: disposeBag)

事件監聽

通過 rx.controlEvent 可以監聽控件的各種事件,且多個事件狀態可以自由組合。很多控件都有 touch 事件,輸入框還有一些獨立事件。

inputTextField.rx.controlEvent([.editingDidBegin]).subscribe(onNext: {
        print("開始編輯")
    }).disposed(by: disposeBag)
        
        inputTextField.rx.controlEvent([.editingChanged]).subscribe(onNext: {
        print("正在編輯")
    }).disposed(by: disposeBag)

UITextView 還有一些特殊的事件

textView.rx.didBeginEditing.subscribe(onNext: {
    print("開始編輯")
}).disposed(by: disposeBag)

textView.rx.didEndEditing.subscribe(onNext: {
    print("結束編輯")
}).disposed(by: disposeBag)

textView.rx.didChange.subscribe(onNext: {
    print("內容發生改變")
}).disposed(by: disposeBag)

textView.rx.didChangeSelection.subscribe(onNext: {
    print("選中部分發生改變")
}).disposed(by: disposeBag)

詳細的介紹可以參考:RxSwift筆記 - RxCocoa 基礎

Driver

如果我們的序列滿足如下特征,就可以使用它:

  • 不會產生 error 事件
  • 一定在主線程監聽(MainScheduler)
  • 共享狀態變化(shareReplayLatestWhileConnected)

2,為什么要使用 Driver?
(1)Driver 最常使用的場景應該就是需要用序列來驅動應用程序的情況了,比如:

  • 通過 CoreData 模型驅動 UI
  • 使用一個 UI 元素值(綁定)來驅動另一個 UI 元素值
    (2)與普通的操作系統驅動程序一樣,如果出現序列錯誤,應用程序將停止響應用戶輸入。
    (3)在主線程上觀察到這些元素也是極其重要的,因為 UI 元素和應用程序邏輯通常不是線程安全的。
    (4)此外,使用構建 Driver 的可觀察的序列,它是共享狀態變化。

RxSwift的使用詳解18(特征序列2:Driver) 這篇文章里 Driver 講的特別詳細。但是 Driver 適合不會發送錯誤信號的,要處理錯誤還是要用 Observable。

MVVM-C

Model - View - ViewModel — Driven by Coordinators
先提一下 MVVM 即模型-視圖-視圖模型。【模型】指的是后端傳遞的數據。【視圖】指的是所看到的頁面?!疽晥D模型】mvvm模式的核心,它是連接view和model的橋梁。

通過將業務邏輯移動到 ViewModel 中,我們將業務邏輯從 ViewController 中分離出來。然而,當我們的目標是可重用的、靈活的代碼時,仍然存在導航問題。一旦在整個視圖或視圖模型中放置導航,就會創建很多耦合,從而使重用代碼變得更加困難。為了解決這一問題,我們將 Coordinator 引入到 MVVM 體系結構中,從而使用了MVVM-C。

詳細介紹一下 MVVM 的每部分的職責:

Coordinator

主要負責流程和導航。

Coordinator 主要職責:

  • 創建并展示新的 ViewController, 以及為該 ViewController 創建 ViewModel 并提供外部依賴項。
  • 需要導航時作為 ViewController 方法的委托。例如: didFinish(), showDetail(), openUrl(),…
  • 創建新的子 Coordinator 作為導航流程的一部分。
  • 使用網絡或者本地存儲等 Service 獲取數據用于導航。最好應該注入 Service 以使其更易于測試。
  • 處理 URI 導航
  • 存儲數據并在模塊之間進行傳輸

Coordinator 不能做的:

  • 修改數據
  • 改變用戶界面
  • 直接使用UI

ViewModel

提供數據并保持 View 或者 ViewController 狀態,因此 ViewModel 根本不需要導入 UIKit 框架。

ViewModel 主要職責:

  • 從提供的 Service 中獲取數據(通過依賴注入的方式獲取,不能直接使用 Service,可以方便進行單元測試)
  • 存儲 ViewController 的狀態
  • 在 ViewModel 的 ViewController 中為子視圖創建、存儲和通信子 ViewController。
  • 決定應該向用戶顯示哪些數據(filter()、map()、分頁)
  • 驗證用戶輸入

ViewModel 不能做的:

  • 顯示任何視圖
  • 決定如何顯示任何數據。
  • 直接使用 Service
  • 頁面跳轉

ViewController 和 View

主要展示視圖和用戶交互。

ViewController 主要職責:

  • 決定如何向用戶顯示數據
  • 通知 coordinator 事件流程

ViewController 不能做的:

  • 決定向用戶展示什么數據
  • 跳轉另一個 ViewController
  • 調用 Service

Service

主要提供數據

Service 的主要職責:

  • 訪問網絡,核心數據,user defaults
  • 將原始數據修改為可讀的形式(解析JSON,將CoreData轉換為簡單對象,…)

服務不能做的:

  • 與其他服務通信
  • 過濾數據

優點

使用MVVM-C模式的好處如下:

  • 解耦: 職責的分離更加清晰,也就是說,當您想要更改與應用程序跳轉相關的內容時,Coordinator 是惟一需要修改的組件。
  • 可測試性: 由于所有 Coordinator 調用都指向一個可替換的 Coordinator,因此 ViewModel 變得更加容易測試。
  • 更改導航: 每個 Coordinator 只負責一個組件,并且沒有與其父組件有任何假設。因此,它可以放在我們想放的任何地方。
  • 代碼重用: 除了可以更改導航,還可以重用組件。例如,可以從應用程序中的多個點調用相同的 Coordinator。

RxSwift & MVVM Demo:
https://github.com/wf96390/RxSwiftMVVMDemo

CSDN博客地址:
https://blog.csdn.net/wf96390/article/details/88370363

參考:
https://www.colabug.com/826982.html
https://blog.csdn.net/qq_32670879/article/details/85158785
http://www.lxweimin.com/p/c30628d60803
https://blog.csdn.net/Mazy_ma/article/details/81913976
http://www.hangge.com/blog/cache/detail_1942.html
https://blog.csdn.net/longshihua/article/details/72801096

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容