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