GitHub Demo鏈接
前言:最近換了新公司,開始了RxSwift+MVVM的爬坑之旅,相比之前臃腫的MVC,使用MVVM架構(gòu)之后,愈發(fā)感慨這些架構(gòu)設(shè)計者的強大??,只要你想,幾乎可以把所有的業(yè)務邏輯從Controller抽離到ViewModel里,讓你的Controller無比清爽!當然這離不開RxSwift的響應式編程方式,它讓MVVM的實現(xiàn)變得簡單,我們今天來講一下ViewModel的不同實現(xiàn)方式。
PS:本篇文章參考了[譯]RxSwift + MVVM: 怎樣搞定 ViewModels,主要思路皆來源于文中的內(nèi)容,原文沒有Demo,我做了一個Demo做補充,想要詳細了解的可以去讀一下這篇。
關(guān)于ViewModel

ViewModel的使用,更像是一個黑盒,我們在Controller里賦予它輸入inputs,ViewModel會將邏輯內(nèi)部處理,然后輸出outputs到view上。
-
第一種ViewModel:外部初始化Input
首先我們來看一下它的結(jié)構(gòu)
protocol TransformViewModelType {
associatedtype Input
associatedtype Output
func transform(input: Input) -> Output
}
需要外部傳入初始化好的input,然后調(diào)用ViewModel 的transform方法輸出output
我們用實際需求來實現(xiàn)一個這種ViewModel,用戶可以在最上部的TextField輸入內(nèi)容,然后點擊Send的Button,數(shù)據(jù)會在下面的Label顯示出來
Simulator Screen Shot - iPhone X? - 2019-04-11 at 13.52.36.png
這里抽離一下要實現(xiàn)的業(yè)務,我們需要的輸入有兩個,一個是textfield的文字,一個是send按鈕的點擊事件,需要的輸出就是文字,具體實現(xiàn)如下:
final class TransformViewModel: TransformViewModelType {
struct Input {
let message: Observable<String>
let send: Observable<Void>
}
struct Output {
let result: Driver<String>
}
func transform(input: TransformViewModel.Input) -> TransformViewModel.Output {
let result = input.send
.withLatestFrom(input.message)
.map { (message) -> String in
return "viewModel1: \(message)"
}
.startWith("result1")
.asDriver(onErrorJustReturn: "")
return Output.init(result: result)
}
}
接下來是output與View的綁定:
func bindModel1() {
let input = TransformViewModel.Input.init(message: textField.rx.text.orEmpty.asObservable(),
send: sendButton.rx.tap.asObservable())
let output = viewModel1.transform(input: input)
output.result
.drive(label_1.rx.text)
.disposed(by: rx.disposeBag)
}
PS:rx.disposeBag來源于NSObject-Rx框架的擴展,是一個非常實用的RxSwift的語法糖。
-
第二種ViewModel:內(nèi)部初始化Input
這種類型我們使用私有的Subject在內(nèi)部做初始化,用戶在創(chuàng)建ViewModel的時候,已經(jīng)完成了相關(guān)邏輯處理的綁定,留下了需要的數(shù)據(jù)接口給外部,可以隨時接入input或者渲染output給View,更加靈活方便一些,我們來看結(jié)構(gòu):
protocol SubjectViewModelType {
associatedtype Input
associatedtype Output
var input: Input {get}
var output: Output {get}
}
不再需要transform方法轉(zhuǎn)化輸出,而是兩個get屬性,還是上面??的需求,我們來看一下這種方式的具體實現(xiàn):
final class SubjectViewModel: SubjectViewModelType {
var input: SubjectViewModel.Input
var output: SubjectViewModel.Output
struct Input {
let message: AnyObserver<String>
let send: AnyObserver<Void>
}
struct Output {
let result: Driver<String>
}
private let messageSubject = PublishSubject<String>()
private let sendSubject = PublishSubject<Void>()
init() {
let result = sendSubject
.withLatestFrom(messageSubject)
.map { (message) -> String in
return "viewModel2: \(message)"
}
.startWith("result2")
.asDriver(onErrorJustReturn: "")
input = Input.init(message: messageSubject.asObserver(), send: sendSubject.asObserver())
output = Output.init(result: result)
}
}
我們使用ViewModel內(nèi)部私有的Subject,在整個ViewModel的init時候,就完成了邏輯處理的綁定,所以不再需要另外的類似transform的邏輯轉(zhuǎn)化,下面??是這種方式與View的數(shù)據(jù)綁定:
func bindModel2() {
textField.rx.text.orEmpty
.subscribe(viewModel2.input.message)
.disposed(by: rx.disposeBag)
sendButton.rx.tap
.subscribe(viewModel2.input.send)
.disposed(by: rx.disposeBag)
viewModel2.output.result
.drive(label_2.rx.text)
.disposed(by: rx.disposeBag)
}
省去了在外部創(chuàng)建input和獲取output的麻煩,看起來是不是更優(yōu)雅一些呢??
對比分析
在最開始提到的參考文章里,作者說第二種類型的ViewModel適用的范圍更廣一些,但是在我的實際使用中,發(fā)現(xiàn)我們要想在View Model中做一些數(shù)據(jù)的中繼,處理更多的邏輯,需要用到Input之外的變量的話,第二種方法明顯有一些無法實現(xiàn)的情況,因為它是在Init方法里做內(nèi)部邏輯的綁定,所以self本身并未初始化,無法讀取自身其他未初始化的數(shù)據(jù);而第一種方式則沒有關(guān)系,因為它的邏輯綁定發(fā)生在transform方法里,這時的ViewModel已經(jīng)初始化完成了。
當然這只是少部分的情況,不可否認,第二種方式的ViewModel使用起來更加的靈活優(yōu)雅,大家看具體的情況來選擇合適的類型即可。
歡迎更正錯誤和交流,回復評論和私信皆可 ??????
