RxSwift MVVM 關(guān)于ViewModel的兩種不同實現(xiàn)

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

圖片來源于網(wǎng)絡

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)雅,大家看具體的情況來選擇合適的類型即可。

歡迎更正錯誤和交流,回復評論和私信皆可 ??????

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容