摘要
本系列文章將詳細分析iOS的MVVMR架構(gòu)模式,并基于Swift的響應(yīng)式、函數(shù)式編程框架RxSwift提供相應(yīng)的實現(xiàn)。
系列共分為兩個部分:
- MVVMR架構(gòu)的思想、基本原理及其初步實現(xiàn)
- 架構(gòu)中某些細節(jié)部分的實現(xiàn)封裝以及實戰(zhàn)
這篇文章講述的是第一部分的內(nèi)容,我會先把架構(gòu)的各個組成粗略地羅列出,然后再對它們進行詳細的分析,最后結(jié)合代碼進行實現(xiàn)。若文章中存在模糊或不合理的地方,還請各位包涵,也歡迎大家向我進行反饋。
前言
寒假期間,我和小組的伙伴們針對團隊之前的一款還未上線的項目進行大重構(gòu),開發(fā)語言從Objective-C轉(zhuǎn)到了Swift,作為組長,我負責了整個項目架構(gòu)的搭建。在舊項目中,我是基于ReactiveCocoa搭起了MVVM這種架構(gòu)模式。鑒于此架構(gòu)模式優(yōu)點頗多,并且在前面也已實踐過,所以在新項目里我也依舊沿用了MVVM,不過用于事件以及數(shù)據(jù)綁定的框架就改用了RxSwift。關(guān)于RxSwift,我在之前也寫過一篇文章: RxSwift進階與實戰(zhàn) ,這篇文章的實戰(zhàn)部分也提供了一個簡單的MVVM架構(gòu)模式實現(xiàn)。
分析下現(xiàn)在移動端開發(fā)比較熱門的幾款架構(gòu)模式:MVC, MVP, MVVM, VIPER,除去最經(jīng)典的模式MVC外,其余的模式究其根本,其實都是從MVC衍變而來,并且都是針對其中的Controller層進行分層再細化,而本文所針對的MVVM架構(gòu)模式,原理上它為了減輕MVC架構(gòu)中Controller層的業(yè)務(wù)負擔,將Controller再細分為兩個部分,一部分跟原本的View層合并在一起形成新的View層,另一部分就變成了ViewModel層。(這里所說的Controller并不能指代iOS中的UIViewController)對于我來說MVP跟MVVM極其相似,如果偏要讓我說出它們的不同,我會主觀地認為MVP中Presentation做的事情只是數(shù)據(jù)與事件的解析轉(zhuǎn)換等業(yè)務(wù)邏輯,而MVVM的ViewModel中除了這些業(yè)務(wù)邏輯外,里面還可以存有某些狀態(tài)變量,像在RAC中,專門有一個宏用于狀態(tài)變量與信號的綁定:RAC(viewModel, userToken) = userTokenSignal,而RxSwift我個人認為是傾向于流的轉(zhuǎn)換,盡量避免出現(xiàn)狀態(tài)變量。所以重構(gòu)項目中有時候我會想: "我TM是在寫MVVM還是MVP?!",算了,不要在意這些細節(jié)...
在上面我一直都在說MVVM模式,但是文章的標題呈現(xiàn)的卻是MVVMR,R其實是我“自作主張”增添的Router(路由器),這個設(shè)計在下面會說到。
接下來我就本此項目的架構(gòu),向大家進行詳細的分析。
MVVMR基本藍圖
首先給大家看兩張示意圖:


這兩張圖分別表示了MVVMR架構(gòu)中的基本構(gòu)成要素和流轉(zhuǎn)換示意。
在圖從,我們可以看到整個架構(gòu)的組成要素分別是:View(視圖)、ViewModel(視圖模型)、Input(輸入)、Output(輸出)、Router(路由器)、UnitCase(單位實例), 其中,View與Input同組成View層,ViewModel與Output組成ViewModel層,UnitCase為一個簡單的數(shù)據(jù)結(jié)構(gòu),用于保存View和ViewModel的關(guān)系,用于后期綁定器對它們相互間進行綁定,這個在后面會詳細說到。
接下來有兩方面需要提及:
架構(gòu)目的
前面說到,傳統(tǒng)的MVC架構(gòu)中,ViewController由于要處理過多的業(yè)務(wù)邏輯以及對View層的顯示邏輯,會變得越來越復(fù)雜,最后將會成為一個重量級角色,在開發(fā)中容易亂了手腳,并且嚴重缺乏可維護性。MVVM架構(gòu)致力于減輕ViewController層的負擔,將一部分屬于純業(yè)務(wù)邏輯處理放到了ViewModel中,而對于View的顯示邏輯,如UIView布局渲染、動畫等就一并歸于View層。
View : 視圖的布局、渲染、動畫、UIViewController的轉(zhuǎn)場
ViewModel : 純業(yè)務(wù)邏輯處理
Model : 提供數(shù)據(jù),如網(wǎng)絡(luò)請求數(shù)據(jù),本地數(shù)據(jù)庫、UserDefaults
有一點需要注意的是,因為MVVM比起MVC來說在層級的數(shù)量上有所增加,所以我們需要再從原來的基礎(chǔ)上多維護了某些東西,這很容易造成架構(gòu)中耦合度的上升,為了降低耦合,我在架構(gòu)中引入了Input以及Output的概念,后面有詳細的分析。
架構(gòu)思想
整套架構(gòu)圍繞著的一個思想是: 事件與數(shù)據(jù)基于流的抽象
我們把事件(如用戶的觸發(fā)事件)以及數(shù)據(jù)(網(wǎng)絡(luò)、本地數(shù)據(jù))抽象成在一條在管道中流動的流,每一次的業(yè)務(wù)處理,都像是一個接入了這條管道中的流處理器,將流入的流轉(zhuǎn)換加工,并輸出處理過后的流。因為事件或數(shù)據(jù)可能會涉及多個不同的業(yè)務(wù)處理,所以在管道中也可以接入多個流處理器,讓事件和數(shù)據(jù)在管道中流動的時候發(fā)生連鎖反應(yīng)。

在本架構(gòu)中,
RxSwift框架就是這條包裹著事件與數(shù)據(jù)流的管道。
各模塊詳解
接下來我就MVVMR架構(gòu)中各重要模塊的概念,結(jié)合iOS的實際開發(fā)來詳細說明。
View
View層做的東西都只是跟視圖有關(guān)系,布局、渲染、動畫等等,并不會接觸與業(yè)務(wù)相關(guān)的內(nèi)容。它匯總視圖的觸發(fā)事件或數(shù)據(jù),構(gòu)建出Input傳入ViewModel,并接受ViewModel傳過來的Output,刷新視圖顯示。
架構(gòu)中,我把UIViewController也歸入了View層中,因為個人覺得ViewController與視圖有著非常密切的關(guān)聯(lián),若要強制性分離職責,應(yīng)該將ViewController里面的所有業(yè)務(wù)邏輯抽離出來,讓UIViewController其只充當View的一部分。
View中還持有路由器Router,用于視圖的跳轉(zhuǎn)。(接下來會說到)
ViewModel
說簡單點,ViewModel做的事情就只有一件: 轉(zhuǎn)換,說復(fù)雜點,ViewModel需要將View傳入的Input中所有的事件數(shù)據(jù)流進行轉(zhuǎn)換處理,最終將完成處理后的流放入Output中傳遞給View,所有的業(yè)務(wù)邏輯都是在這里進行實現(xiàn),其中涉及到數(shù)據(jù)的請求(網(wǎng)絡(luò)、本地)需要向Model請求獲取。
Model
模型層,數(shù)據(jù)提供者。提供網(wǎng)絡(luò)請求數(shù)據(jù),本地數(shù)據(jù)庫、UserDefaults緩存數(shù)據(jù),支持對數(shù)據(jù)進行解析處理。不過在實際項目中,Model層并沒有很明顯地表示出來,我是將網(wǎng)絡(luò)請求、JSON數(shù)據(jù)解析和本地緩存封裝在一起構(gòu)建出一個較為強大的“流轉(zhuǎn)換器”,其也算是Model層的一部分。相關(guān)網(wǎng)絡(luò)請求的封裝我會在下一篇中談到。
Input & Output
Input和Output其實是一個容器,里面裝載著各種事件數(shù)據(jù)流,在View與ViewModel通信中起到傳遞的作用。
看到這里,可能有人會認為,View與ViewModel之間的相互通信較為簡單,只需通過方法去調(diào)用即可,沒必要又另外再構(gòu)建多一個Input和Output。其實,在框架設(shè)計中,我構(gòu)建了這兩個東西,主要目的就是實現(xiàn)View層與ViewModel層的完全解耦。架構(gòu)在工作的時候,View以及ViewModel各自維護著自己的運作,且它們之間不存在過多的耦合,即兩層之間互不關(guān)注,也互不知道對方的實際情況,它們間的通信只依賴于Input和Output。這樣,在開發(fā)以及后期的維護中,我們在對其中一層進行修改或重構(gòu)時,另外一層可完全不需要改動。
在實際項目中,我使用的是Swift的struct(結(jié)構(gòu)體)去實現(xiàn)Input和Output,并將每個事件數(shù)據(jù)流作為結(jié)構(gòu)體中的屬性來持有。這樣做的好處是每個事件數(shù)據(jù)流都能清晰明了地列舉在代碼中,通過觀察Input的屬性,我們能知道View能夠產(chǎn)出多少種視圖觸發(fā)的事件數(shù)據(jù)流,通過觀察Output的屬性,我們也得知View最終要接收哪些更新視圖的事件數(shù)據(jù)流。
這里總結(jié)下設(shè)計Input與Output的目的:
- 實現(xiàn)
View與ViewModel層之間的解耦 - 能夠清晰羅列出各種事件數(shù)據(jù)流
Binder & UnitCase
首先來說下Binder(綁定器),它要做的事情就是將View以及ViewModel進行綁定。這里就拋出了一個問題: 我們?yōu)槭裁葱枰壎ǎ?/em>
我們知道,iOS應(yīng)用是以頁面為單元的,一個頁面就是一套MVC或MVVM工作的結(jié)果。而普通的應(yīng)用本身是擁有非常多的頁面,若我們使用的架構(gòu)模式是MVVM,這就需要創(chuàng)建同等數(shù)量的若干套MVVM,而MVVM的構(gòu)建需要將各層各模塊聯(lián)系綁定在一起。若每次我們在需要跳轉(zhuǎn)到一個新頁面時才去對架構(gòu)進行綁定,這就增大了代碼的復(fù)雜度以及冗余度,所以,我們需要一套機制,在我們需要呈現(xiàn)一個新頁面時,自動幫我們將架構(gòu)各層各模塊進行綁定。在MVVMR架構(gòu)中,我使用的是Binder來實現(xiàn)這種機制。
但是,在綁定時,我們必須要將View跟ViewModel一一對應(yīng)起來,不可能說將一個頁面的View跟其他頁面的ViewModel進行綁定。所以,為了明確這種一一對應(yīng)關(guān)系,我引入了UnitCase,它是一個存有View和ViewController對應(yīng)關(guān)系的容器。通過UnitCase,Binder就能正確綁定View和ViewModel層。
Router
路由器就是為了實現(xiàn)各頁面之間的跳轉(zhuǎn)。為什么架構(gòu)中不直接使用iOS API提供的pushViewController、presentViewController等方法呢?這里考慮到兩個問題:
- 頁面需要創(chuàng)建后才能夠進行跳轉(zhuǎn),而在創(chuàng)建頁面的時候需要進行綁定。如果將頁面綁定跟跳轉(zhuǎn)封裝在一起將帶來較大的便利。
- 頁面間需要傳遞事件和數(shù)據(jù),通常的做法是使用方法傳遞(正向傳遞)或者代理模式(反向傳遞),這樣做耦合度較大。需要一種實現(xiàn)頁面間傳遞數(shù)據(jù)且耦合度較低的機制。
路由器就是解決以上問題的優(yōu)雅方案,它與Binder(綁定器)密切結(jié)合,并提供頁面間數(shù)據(jù)傳遞的接口。所以,這篇文章所講述的架構(gòu)模式名為MVVMR(傳統(tǒng)MVVM+Router)。
架構(gòu)實現(xiàn)
下面就是"Show My Code"的時間,以下我會貼出初步實現(xiàn)MVVMR架構(gòu)的相關(guān)代碼,對于一些更為細節(jié)的實現(xiàn)封裝,我會在下一篇文章中談到。
協(xié)議部分
Swift作為一門傾于“面向協(xié)議”編程范式的語言,編寫的時候當然要更好地去發(fā)揮其協(xié)議的作用。
Input & Output Protocol
/// Input Output
protocol ViewToViewModelInput {
init(view: MVVMView)
}
protocol ViewModelToViewOutput {
init(viewModel: MVVMViewModel)
}
上面就是Input和Output的協(xié)議定義,從名字上可以很清晰地看出它們的作用,一個是從View傳遞到ViewModel,而另一個則是反過來傳遞。它們的構(gòu)造都需要自身的發(fā)出者。
Provider Protocol
/// Provider
protocol ViewToViewModelInputProvider {
var inputType: ViewToViewModelInput.Type? { get }
func provideInput() -> ViewToViewModelInput?
}
extension ViewToViewModelInputProvider where Self: MVVMView {
func provideInput() -> ViewToViewModelInput? {
return self.inputType?.init(view: self)
}
}
protocol ViewModelToViewOutputProvider {
var outputType: ViewModelToViewOutput.Type? { get }
func provideOutput() -> ViewModelToViewOutput?
}
extension ViewModelToViewOutputProvider where Self: MVVMViewModel {
func provideOutput() -> ViewModelToViewOutput? {
return self.outputType?.init(viewModel: self)
}
}
Provider(提供者)是針對Input跟Output的構(gòu)建而設(shè)計的,意為Input與Output的提供者。每個提供者里具有一個元類類型的屬性以及一個提供方法,而在分類中,提供方法已經(jīng)幫我們?nèi)崿F(xiàn)了。所以在實現(xiàn)提供者協(xié)議的時候,我們只需提供相應(yīng)的Input或Output類型即可。
View 和 ViewModel 實現(xiàn)
View以及ViewModel的實現(xiàn),我是擬好了兩個抽象類:MVVMView和MVVMViewModel。
// MARK: - View & ViewModel
class MVVMView: UIViewController, ViewToViewModelInputProvider {
private let viewModelType: MVVMViewModel.Type?
private(set) var router: Router!
var viewModel: MVVMViewModel?
var inputType: ViewToViewModelInput.Type? { return nil }
var receive: Driver<Any>?
required init(_ viewModelType: MVVMViewModel.Type?) {
self.viewModelType = viewModelType
super.init(nibName: nil, bundle: nil)
self.router = Router(from: self)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
if let viewModelType = self.viewModelType, let input = self.provideInput() {
self.viewModel = viewModelType.init(input: input)
if let output = self.viewModel!.provideOutput() {
rxDrive(viewModelOutput: output)
}
}
}
func rxDrive(viewModelOutput: ViewModelToViewOutput) { crash("抽象方法,在此進行綁定,此方法必須重寫!") }
func provideCallBack() -> Driver<Any>? { return nil }
let disposeBag = DisposeBag()
}
class MVVMViewModel: NSObject, ViewModelToViewOutputProvider {
let input: ViewToViewModelInput
var outputType: ViewModelToViewOutput.Type? { return nil }
required init(input: ViewToViewModelInput) {
self.input = input
}
}
我們先看這兩個抽象類的繼承以及協(xié)議關(guān)系:MVVMView繼承的是UIViewController,所以這時候UIViewController作為View層的一員,只負責視圖的顯示相關(guān),并不會接觸到業(yè)務(wù)邏輯的處理。MVVMViewModel則簡單地繼承NSObject。這兩個抽象類都是實現(xiàn)了Provider協(xié)議,擔任了Input和Output的提供職責。在提供者協(xié)議實現(xiàn)元類型的屬性時,我返回的是nil,若繼承抽象類的子類沒有重寫此屬性,則告訴框架自己并沒有提供了Input或Output,對此框架就取消對它們的綁定。
我們可以看到,MVVMView對MVVMViewModel進行了強引用,我們只需持有MVVMView實例,就能依舊維持MVVMViewModel的存在;并且,MVVMViewModel在MVVMView中的訪問修飾為private,因此所有繼承MVVMView都抽象類都無法訪問此屬性,做到了兩者的高度解耦。
這里在詳細看回MVVMView,其具有router(路由器)屬性,通過路由器,我們可以進行頁面的跳轉(zhuǎn);另外有幾個抽象的屬性和方法:
- receive : 用于頁面的通信,與
Router(路由器密切相關(guān)),當此頁面收到上一個頁面所傳遞進來的事件數(shù)據(jù)時,receive就會賦入這些信息。在設(shè)計上,receive的賦值緊接在MVVMView的初始化之后,所以,我們不可以在重寫的初始化方法中獲取receive的值。 - rxDrive(viewModelOutput:) : 這個方法就是用于將從
ViewModel層出來的事件數(shù)據(jù)流驅(qū)動整個頁面的顯示,這方法中,我們可以通過傳進來的參數(shù)Ouput驅(qū)動視圖的刷新顯示、進行頁面的跳轉(zhuǎn)。這個方法也是ViewModel層向View層傳遞信息的唯一出口。 - provideCallBack() : 此方法是用于頁面跳轉(zhuǎn)中的反向數(shù)據(jù)傳遞,即將數(shù)據(jù)從本頁面?zhèn)鞯缴弦粋€頁面,因為我們使用的是響應(yīng)式的
RxSwift框架,所以數(shù)據(jù)的反向傳遞就不需要使用到閉包或代理模式。在后面的Router實現(xiàn)中會說到它的機制。 - disposeBag : 用于
RxSwift資源的回收。
在MVVMView的viewDidLoad()方法中,我們進行MVVMView和MVVMViewModel的關(guān)聯(lián),如果MVVMView和MVVMViewModel沒有提供Input和Output,則表明此時View和ViewModel層沒有通信,所以也就不會調(diào)用rxDrive方法了。
UnitCase
// MARK: - Unit
struct MVVMUnit {
let viewType: MVVMView.Type
let viewModelType: MVVMViewModel.Type
}
extension MVVMUnit: ExpressibleByArrayLiteral {
typealias Element = AnyClass
init(arrayLiteral elements: Element...) {
guard elements.count == 2 else { crash("單元初始化參數(shù)長度錯誤") }
guard let viewType = elements[0] as? MVVMView.Type else { crash("單元初始化參數(shù)類型錯誤") }
guard let viewModelType = elements[1] as? MVVMViewModel.Type else { crash("單元初始化參數(shù)類型錯誤") }
self.viewType = viewType
self.viewModelType = viewModelType
}
}
struct MVVMUnitCase: RawRepresentable {
typealias RawValue = MVVMUnit
let rawValue: MVVMUnit
init(rawValue: RawValue) {
self.rawValue = rawValue
}
}
上面的代碼使用到了一個函數(shù)crash(_ message:),為斷言函數(shù),這里就不需要給出具體實現(xiàn)了。
我們先來看MVVMUnit,它具有兩個元類型的屬性,分別代表一個頁面中的MVVMView和MVVMViewModel類型,通過這種關(guān)系,綁定器就能正確地按照一一對應(yīng)關(guān)系綁定MVVMView和MVVMViewModel。MVVMUnit還實現(xiàn)了ExpressibleByArrayLiteral,我們可以直接簡便地通過數(shù)組字面量來初始化MVVMUnit。
而MVVMUnitCase則是對MVVMUnit的再一次封裝,其實現(xiàn)了RawRepresentable協(xié)議,這樣我們就能像使用枚舉一樣通過點.語法來創(chuàng)建它。
使用的話我這里舉個例子,加入現(xiàn)在我們的項目中需要用到兩個頁面,一個是主頁面"main",一個是登錄頁面"login",它們都有對應(yīng)的MVVMView和MVVMViewModel:MainMVVMView、MainMVVMViewModel、LoginMVVMView、LoginMVVMViewModel,我們則需要在MVVMUnitCase中進行添加:
extension MVVMUnitCase {
static let main = MVVMUnitCase(rawValue: [MainMVVMView.self, MainMVVMViewModel.self])
static let simpleInfo = MVVMUnitCase(rawValue: [LoginMVVMView.self, LoginMVVMViewModel.self])
}
Binder
// Binder
struct MVVMBinder {
/// 根據(jù)標識符獲取視圖,會在背后做視圖與視圖模型的綁定
///
/// - Parameter identifier: 標識符
/// - Returns: 返回已經(jīng)綁定好了的視圖
static func obtainBindedView(_ unitCase: MVVMUnitCase) -> MVVMView {
let unit = unitCase.rawValue
let viewType = unit.viewType
let viewModelType = unit.viewModelType
let view = viewType.init(viewModelType)
return view
}
}
綁定器做的事情是對MVVMView和MVVMViewModel的綁定,它具有一個靜態(tài)方法obtainBindedView(_ unitCase:)在這個方法中我們需要傳入一個MVVMUnitCase的實例,然后綁定器會幫我們創(chuàng)建MVVMView和MVVMViewModel實例并進行綁定,最后將返回MVVMView的實例,我們拿到這個實例就能進行頁面的跳轉(zhuǎn)。
Router
// MARK: - Router
enum RouterType {
case push(MVVMUnitCase)
case present(MVVMUnitCase)
case root(MVVMUnitCase)
case back
}
struct Router {
let from: MVVMView
init(from: MVVMView) {
self.from = from
}
func route(_ type: RouterType, send: Driver<Any>? = nil) -> Driver<Any>? {
switch type {
case let .push(unitCase):
let view = MVVMBinder.obtainBindedView(unitCase)
view.receive = send
from.navigationController?.pushViewController(view, animated: true)
return view.provideCallBack()
case let .present(unitCase):
let view = MVVMBinder.obtainBindedView(unitCase)
view.receive = send
from.present(view, animated: true, completion: nil)
return view.provideCallBack()
case let .root(unitCase):
let view = MVVMBinder.obtainBindedView(unitCase)
view.receive = send
UIApplication.shared.keyWindow?.rootViewController = view
return view.provideCallBack()
case .back:
if from.presentationController != nil {
from.dismiss(animated: true, completion: nil)
} else {
_ = from.navigationController?.popViewController(animated: true)
}
return nil
}
}
}
extension MVVMView {
func route(_ type: RouterType, send: Driver<Any>? = nil) -> Driver<Any>? {
return self.router.route(type, send: send)
}
}
常見的頁面跳轉(zhuǎn)為導(dǎo)航控制器的Push、Pop,模態(tài)的Present、Dismiss,UIWindow的rootViewController(根視圖控制器切換),我們把這些跳轉(zhuǎn)作為一個枚舉設(shè)計了RouterType,其中back則代表Pop和Dismiss,在使用RouterType時,我們將目標的MVVMUnitCase作為枚舉的關(guān)聯(lián)值傳入。
對于路由器Router,我們需要使用一個MVVMView來初始化它,代表跳轉(zhuǎn)是從這個MVVMView開始的。調(diào)用路由器中的route(_ type: , send:)方法就能進行頁面的跳轉(zhuǎn),其中,參數(shù)send就是要傳遞到下一個頁面的事件數(shù)據(jù),而方法的返回值則為下一個頁面反向傳遞過來的事件數(shù)據(jù),通過MVVMView的抽象方法provideCallBack()。
我也為MVVMView創(chuàng)建了一個擴展,在擴展中我們可以直接調(diào)用自身路由器的路由方法。
這里貼出個路由器的使用例子:
_ = view.route(.push(.login), send: Driver.just(userToken))
因為MVVMUnitCase實現(xiàn)了RawRepresentable協(xié)議,所以我們可以直接通過點語法來取得登錄的Unit: .login
架構(gòu)使用
到此,整套架構(gòu)的實現(xiàn)就基本完成了,下面我們來結(jié)合RxSwift來構(gòu)建一個使用此套架構(gòu)的Demo:
View 層
class DemoMVVMView: MVVMView {
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.mButton)
}
fileprivate lazy var mButton: UIButton = {
$0.frame = self.view.bounds
$0.setTitle("點擊", for: .normal)
return $0
}(UIButton())
override var inputType: ViewToViewModelInput.Type? { return DemoInput.self }
override func rxDrive(viewModelOutput: ViewModelToViewOutput) {
let output = viewModelOutput as! DemoOutput
output.color.drive(self.rx.updateButtonBackgroundColor).addDisposableTo(self.disposeBag)
output.title.drive(self.rx.updateButtonTitle).addDisposableTo(self.disposeBag)
}
}
// MARK: - Reactive
extension Reactive where Base: DemoMVVMView {
var updateButtonTitle: AnyObserver<String> {
return UIBindingObserver<Base, String>(UIElement: base) { view, newTitle in
view.mButton.setTitle(newTitle, for: .normal)
}.asObserver()
}
var updateButtonBackgroundColor: AnyObserver<UIColor> {
return UIBindingObserver<Base, String>(UIElement: base) { view, newColor in
view.mButton.backgroundColor = newColor
}.asObserver()
}
}
// MARK: - Input
struct DemoInput: ViewToViewModelInput {
let refresh: Driver<()>
init(view: MVVMView) {
let view = view as! DemoMVVMView
self.refresh = view.mButton.rx.tap.asDriver()
}
}
可以看到,我們在DemoMVVMView中添加了一個按鈕,按鈕的點擊事件作為從View層傳入ViewModel層的事件數(shù)據(jù)流,所以在DemoInput中我們定義了按鈕點擊的刷新流。
在屬于DemoMVVMView的Reactive分類中,里面的觀察者代表DemoMVVMView接收到ViewModel層傳來的事件數(shù)據(jù)流時進行的驅(qū)動操作,為更新按鈕的標題和切換按鈕的背景色。
我們重寫inputType屬性以及rxDrive方法,在inputType屬性中返回DemoInput類型,在rxDrive方法中將ViewModel傳過來的Output驅(qū)動視圖的刷新。
ViewModel 層
// MARK: - ViewModel
class DemoMVVMViewModel: MVVMViewModel {
override var outputType: ViewModelToViewOutput.Type? { DemoOutput.self }
}
struct DemoOutput: ViewModelToViewOutput {
let color: Driver<UIColor>
let title: Driver<String>
init(viewModel: MVVMViewModel) {
let viewModel = viewModel as! DemoMVVMViewModel
let input = viewModel.input as! DemoInput
self.color = input.refresh.map { _ in UIColor.orange }
self.title = input.refresh.map { _ in "數(shù)據(jù)已刷新" }
}
}
ViewModel層相對較為簡單,因為現(xiàn)在還未涉及網(wǎng)絡(luò)請求操作或數(shù)據(jù)庫操作,也沒有進行某些業(yè)務(wù)邏輯的判斷處理,所以DemoMVVMViewModel中只有一個重寫的OutputType屬性。在實際開發(fā)中,MVVMViewModel中會持有某些臨時的狀態(tài)變量,或網(wǎng)絡(luò)、本地數(shù)據(jù)庫框架實例。
對于流的轉(zhuǎn)換,發(fā)生在Output的初始化方法中,可能有人會疑惑: “流的轉(zhuǎn)換不是發(fā)生在MVVMViewModel中的嗎,為什么會在Output的初始化方法中?” 在前面我說到,流的轉(zhuǎn)換是在ViewModel層發(fā)生的,而ViewModel層是包含Output跟MVVMViewModel的,并不是說MVVMViewModel名字的關(guān)系所以MVVMViewModel就代表整個ViewModel層。在此架構(gòu)中,MVVMViewModel主要是用于持有某些狀態(tài)變量、某些服務(wù)模塊和提供Output類型的。
MVVMUnitCase
不要忘記在UnitCase中對剛構(gòu)建好的MVVM進行配置:
extension MVVMUnitCase {
static let demo = MVVMUnitCase(rawValue: [DemoMVVMView.self, DemoMVVMViewModel.self])
}
在項目開發(fā)中,我們就可以在任意MVVMView中進行向"Demo"頁面的跳轉(zhuǎn)了:
_ = self.route(.push(.demo))
上面展示的架構(gòu)使用Demo較為簡單,在下一篇文章中,我會結(jié)合封裝完成后的網(wǎng)絡(luò)請求框架,對MVVMR架構(gòu)進行一此較為大型的實戰(zhàn)。
總結(jié)
這篇文章詳細說明了我在項目中搭建出來的MVVMR架構(gòu)其思想、基本原理以及初步實現(xiàn),為基于RxSwift的MVVMR架構(gòu)系列文章的第一篇。若大家發(fā)現(xiàn)文章中的內(nèi)容存在問題或者有更好的建議,歡迎向我反饋!
在下一篇文章中,我會對架構(gòu)中的某些細節(jié)進行實現(xiàn)與封裝,如基于Moya + Argo的網(wǎng)絡(luò)框架封裝,并在后面使用架構(gòu)進行實戰(zhàn)使用。