并不是說(shuō)Rx就是MVVM,只是用了Rx,才能更有MVVM的趕腳,畢竟iOS原生的MVC框架沒(méi)那么好改變~
所以你以為接下來(lái)要寫什么是MVVM了嘛?自己去百度吧
所以你以為接下來(lái)我要寫什么是Rx了嘛?自己去Github吧
首先有些概念
1. MVVM有哪些文件
簡(jiǎn)單來(lái)說(shuō)就是我們有Controller和ViewModel兩個(gè)文件,去做一坨東西,Controller操作UI,ViewModel操作業(yè)務(wù),(嗯,這個(gè)是整篇里說(shuō)的最官方的了)
2. Controller和ViewModel怎么交互?
當(dāng)然如果我說(shuō)Controller里寫viewModel.doSth()這樣,你肯定就不會(huì)看下去了;
所以我們引入一個(gè)【節(jié)點(diǎn)】的概念,節(jié)點(diǎn)有兩三種,
一種是凸凸的節(jié)點(diǎn),代表著他會(huì)向外發(fā)射一個(gè)能量,至于這能量是個(gè)啥,你先別管~對(duì)于凸凸,通常情況下你只需要給他來(lái)一發(fā)就完了,也可能你需要包裝一下里面的能量,比如把貓糧裝到盒子包起來(lái)然后再發(fā)出去
一種是凹凹的節(jié)點(diǎn),代表著他需要外面給他來(lái)一發(fā)能量,這個(gè)要做的事情比較多,你收到能量后需要拿著他去做接下來(lái)的事,這是你對(duì)應(yīng)的凸凸需要你完成的,或者說(shuō)你需要把快遞拆了,然后把貓糧拆了,然后去喂貓,然后觀察貓是不是正常,是不是喜歡吃,然后如果不正常還需要退貨去罵人~
一種是凹凸的節(jié)點(diǎn),這就很牛皮了他既能來(lái)一發(fā)又能收一發(fā),所以上面提到的他都要干
而至于能量/快遞盒子是神馬?方法傳遞時(shí)候不是需要帶參數(shù)么,你把參數(shù)當(dāng)成能量也闊以~
有點(diǎn)抽象?看下面
class ViewController: UIViewController {
let viewModel = ViewModel()
func buttonTapped()
{
viewModel.dosth(一發(fā)能量)
}
}
這是普通版本的,而在節(jié)點(diǎn)之下,Controller中會(huì)有個(gè)凸凸的節(jié)點(diǎn),而ViewModel就會(huì)有個(gè)凹凹的節(jié)點(diǎn),然后用厲害的手段把他們結(jié)合起來(lái)。
class ViewController: UIViewController
{
let signalOutput = 凸凸<能量>()
override func viewDidLoad() {
// 把凸凸和凹凹結(jié)合到一起
// 別想那些羞射的事情,不是那樣的!
putTogether(signalOutput , signalInput)
}
}
class ViewModel
{
let signalInput = 凹凹<能量>()
}
所以說(shuō)白了就是:
我們把所有方法調(diào)用變成了屬性,所以我們不再存在方法調(diào)用了,方法會(huì)濃縮到屬性里去,當(dāng)然你如果把【凹凹】理解成封裝了個(gè)closure,那我覺(jué)得你已經(jīng)領(lǐng)悟了很多了~,所以下面你應(yīng)該有這么個(gè)概念,
Button點(diǎn)擊之類的事情,我就需要有個(gè)【凸凸】,同時(shí)我需要在ViewModel中找個(gè)【凹凹】,把他們捏一起讓他們肚子里的能量傳遞起來(lái),
不過(guò)Button點(diǎn)下去,你啥都沒(méi),所以只是一個(gè)空的能量在那傳遞,傳遞還是會(huì)傳,只是沒(méi)什么內(nèi)容,就像你收到一個(gè)空的快遞盒子一樣~
還是不理解的話試一下最后一個(gè)場(chǎng)景,把凸凸想成送快遞的,把凹凹想成收快遞的,而里面的能量就是快遞盒子,再想一下,如果還想不通,那就回過(guò)去再看看吧~
所以我們從三個(gè)簡(jiǎn)單的實(shí)際場(chǎng)景看下,在我當(dāng)前的思維模式下,是怎么把簡(jiǎn)單問(wèn)題變得灰常復(fù)雜的,這其實(shí)就是MVVM干的事情~
場(chǎng)景一:支付場(chǎng)景下的簡(jiǎn)單判斷
現(xiàn)在有個(gè)按鈕,點(diǎn)擊后觸發(fā)一個(gè)請(qǐng)求告訴我們用戶有木有設(shè)置過(guò)支付密碼/
TouchId/甚至FaceId,怎么高端你就怎么想;如果有設(shè)置過(guò),直接讓用戶輸入密碼/摸手機(jī)/秀臉,操作完成后帶著密碼提交請(qǐng)求,然后完成支付。流程結(jié)束
如果木有設(shè)置過(guò),那就跳轉(zhuǎn)到設(shè)置頁(yè)面結(jié)束。流程結(jié)束
這應(yīng)該描述起來(lái)很簡(jiǎn)單,但是如果按照普通的寫法,不是Controller會(huì)變的很肥,就是流程間的交互Closure會(huì)很多
所以該怎么把這個(gè)看著簡(jiǎn)單的東西解析成看著灰常復(fù)雜的場(chǎng)景呢~
1. 首先我們要知道Controller要做些啥
- 按鈕點(diǎn)擊
- 彈個(gè)密碼框讓用戶輸
- 跳轉(zhuǎn)去設(shè)置密碼
上面是在MVVM中,Controller需要做的事,只有這些,沒(méi)有別的了
所以這三個(gè)事情一定會(huì)對(duì)應(yīng)三個(gè)節(jié)點(diǎn)了,那哪些是凸凸哪些是凹凹呢?
對(duì)于Controller來(lái)說(shuō)很簡(jiǎn)單的概念就是,所有從View觸發(fā)/或者說(shuō)從界面觸發(fā)的東西都是凸凸,比如按鈕點(diǎn)擊,比如列表cell點(diǎn)擊等。
所以上面這三個(gè)
- 按鈕點(diǎn)擊 【我是凸凸】
- 輸入完成密碼【我是凸凸】
- 彈個(gè)密碼框讓用戶輸 【我是凹凹】
- 跳轉(zhuǎn)去設(shè)置密碼 【我是凹凹】
可能我們會(huì)問(wèn),為什么下面2個(gè)是凹凹,因?yàn)樗灰彩怯|發(fā)UI嘛?但是如果這樣問(wèn),誰(shuí)讓你的按鈕點(diǎn)擊了,誰(shuí)讓你彈個(gè)密碼框了,誰(shuí)讓你跳轉(zhuǎn)了,你就知道了,除了第一第二個(gè)是用戶點(diǎn)的,其他兩個(gè)其實(shí)都是別的代碼讓你觸發(fā)的,所以在這兩個(gè)文件中,只有可能是ViewModel讓Controller去做了這兩件事
2. ViewModel要做些啥
- 一個(gè)查詢是不是有支付密碼的請(qǐng)求
- 處理是不是有支付密碼然后分發(fā)
- 告訴
Controller彈出密碼框 - 告訴
Controller去設(shè)置密碼 - 接受個(gè)密碼
- 一個(gè)提交支付密碼的請(qǐng)求
- 告訴
Controller支付成功了
ViewModel要做的事就多了,但是我們簡(jiǎn)單解析后就這些了,還是和上面一樣,我們把凸凸凹凹分析一下,ViewModel如果理解了,那就真的明白這個(gè)概念了,其實(shí)增的很簡(jiǎn)單。
- 一個(gè)查詢是不是有支付密碼的請(qǐng)求 【凹凹】
- 處理是不是有支付密碼然后分發(fā) 【凹凹】
- 告訴
Controller彈出密碼框 【凸凸】 - 告訴
Controller去設(shè)置密碼 【凸凸】 - 接受個(gè)密碼 【凹凹】
- 一個(gè)提交支付密碼的請(qǐng)求 【凹凹】
- 告訴
Controller支付成功了 【凸凸】
其實(shí)凸凸很好理解,都告訴Controller做事了,還不是凸凸也真是服。而請(qǐng)求發(fā)起者在這個(gè)場(chǎng)景下都是來(lái)自于Controller,而第二個(gè)處理是否有密碼的節(jié)點(diǎn)其實(shí)只是個(gè)過(guò)度節(jié)點(diǎn),而同樣的過(guò)度還會(huì)在請(qǐng)求中出現(xiàn),當(dāng)然這之后再說(shuō);
所以查詢支付密碼后,請(qǐng)求會(huì)有個(gè)能量傳遞給【處理是否有支付密碼的分發(fā)】,這就屬于內(nèi)部凸凸凹凹了,用的順的話這就會(huì)很習(xí)慣。
在有了這些概念后來(lái)看張整合的圖:

這里有幾個(gè)是內(nèi)部向下箭頭,其實(shí)凸凸也是可以接受內(nèi)部來(lái)的能量的,換句話說(shuō)他對(duì)外可能是凸凸,對(duì)內(nèi),那就未必咯~
3. Controller的凹凹
首先你要知道凸凸和凹凹是個(gè)啥~,我們就非常生硬的引入了RxSwift的概念,反正只是個(gè)類名而已,不會(huì)影響大家任何思路:
我們把單個(gè)節(jié)點(diǎn)定義成:
let signalOutput = PublishRelay<Void>()
其中PublishRelay就是凸凸和凹凹的類型,其實(shí)他們是同一個(gè)類的,只是從功能上區(qū)分了凸和凹的概念,在代碼上沒(méi)什么差別。
另外PublishRelay是PublishSubject的簡(jiǎn)單封裝,作用一毛一樣,區(qū)別就是前者在RxCocoa中,而后者在RxSwift中,所以單純用這個(gè),我們就可以來(lái)看下Controller會(huì)變成什么樣:
class ViewController: UIViewController {
let button = UIButton()
// 按鈕點(diǎn)擊
let buttonTapOutput = PublishRelay<Void>()
// 跳轉(zhuǎn)去設(shè)置支付密碼
let gotoSetpswInput = PublishRelay<Void>()
// 彈密碼輸入框
let showSetpswInput = PublishRelay<Void>()
// 輸入密碼完成
let finishedSettingpswOutput = PublishRelay<String>()
// 流程完成
let allFinishedInput = PublishRelay<String>()
override func viewDidLoad() {
button.rx.tap
.bind(to: buttonTapOutput)
.disposed(by: rx.disposeBag)
gotoSetpswInput.subscribe(onNext: { (_) in
// 此處添加navigate跳轉(zhuǎn)邏輯
print("跳轉(zhuǎn)啦啦啦")
}).disposed(by: rx.disposeBag)
showSetpswInput.subscribe(onNext: { (_) in
// 此處添加彈出密碼設(shè)置
print("設(shè)置密碼啦啦啦")
}).disposed(by: rx.disposeBag)
allFinishedInput.subscribe(onNext: { (_) in
print("all done")
}).disposed(by: rx.disposeBag)
}
}
如果無(wú)法理解,你可以理解成bind就是把凸凸和凹凹結(jié)合的方法,當(dāng)然也可以連接兩個(gè)凸凸,這樣相當(dāng)于兩個(gè)快遞員之間的傳遞,而subscribeNext 的用法在這就是給凹凹添加一系列處理事件,他在獲取到能量后需要做一堆事,比如我們可以在設(shè)置密碼處彈框,在跳轉(zhuǎn)處寫navigationController.push()...
再詳細(xì)點(diǎn)描述,其實(shí)
bind和subscribe的用法是一樣的,只是bind給另一個(gè)節(jié)點(diǎn),相當(dāng)于多了個(gè)快遞傳送,但是最后的closure肯定需要的,只是你什么時(shí)候?qū)懚?,?code>subscribe就是你懶的找快遞了直接開(kāi)箱驗(yàn)貨了而已
上面的代碼中其實(shí)只處理了Controller的input,在那張圖上就是箭頭指向Controller的部分,因?yàn)檫@就是Controller要做的事,所以在MVVM崇尚VC/VM分離的情況下,我們可以毫無(wú)干涉的先把Controller要做的部分完善掉,無(wú)需考慮任何別的業(yè)務(wù)邏輯,這也是隔離的一個(gè)好處
4. ViewModel的凹凹
從上面這點(diǎn)我們可以看出,當(dāng)我們劃分清楚了凹凹凸凸,我們的開(kāi)發(fā)順序會(huì)變的順暢,處理完Controller的凹凹之后,我們接下來(lái)需要處理ViewModel的凹凹,因?yàn)榘及级际钱?dāng)前類肚子中的邏輯,所以會(huì)很好做
class ViewModel {
let disposeBag = DisposeBag()
// 查詢是否有支付密碼,帶請(qǐng)求
let checkPswInput = PublishRelay<Void>()
// 處理查詢結(jié)果
let checkeNeedPswDispatch = PublishRelay<Bool>()
// 去設(shè)置密碼
let gotoSetpswOutput = PublishRelay<Void>()
// 彈出框輸入密碼
let showSetpswOutput = PublishRelay<Void>()
// 密碼輸入完成
let setPswFinishInput = PublishRelay<String>()
// all done
let finishedAllOuput = PublishRelay<Void>()
init() {
// flatMap后的內(nèi)容其實(shí)應(yīng)該用一個(gè)請(qǐng)求代替,這里簡(jiǎn)略一下直接把返回值標(biāo)出來(lái)了
// 一般Rx的請(qǐng)求返回的結(jié)果就是Observable<T>的
checkPswInput
.flatMap { Observable.just(true) }
.bind(to: checkeNeedPswDispatch)
.disposed(by: disposeBag)
checkeNeedPswDispatch.subscribe(onNext: { (hasPsw) in
if hasPsw {
self.showSetpswOutput.accept(())
}
else {
self.gotoSetpswOutput.accept(())
}
}).disposed(by: disposeBag)
setPswFinishInput
.flatMap { _ in Observable.just(true) }
.map { _ in () }.bind(to: finishedAllOuput)
.disposed(by: disposeBag)
}
}
因?yàn)橹攸c(diǎn)不在請(qǐng)求,所以我們簡(jiǎn)單忽略請(qǐng)求部分,把最終結(jié)果貼了上來(lái),flatMap和Map簡(jiǎn)單來(lái)說(shuō)就是快遞拿到你的包裹給你加了個(gè)盒子或者拆了個(gè)盒子換了個(gè)信封之類的,雖然這很不道德,但是在Rx中這再正常不過(guò),雖然這有點(diǎn)響應(yīng)式的概念,如果想了解的話可以點(diǎn)一下看看,不過(guò)如果看不懂:
所謂響應(yīng)式,就是你在網(wǎng)上買了袋貓糧,賣家把貨給一個(gè)快遞,途中他可能給你裝盒子,拆盒子,拿出來(lái)嘗一口,給你換一袋便宜的,交給另一個(gè)快遞小哥,搭個(gè)飛機(jī)乘個(gè)坦克,然后給你重新買袋正品,最后送到你的手上的還是一袋東西,那至于他是不是你想要的貓糧,就要看中間的過(guò)程了,但是這件事就是有始有終的一整個(gè)工作流,途中凹凹凸凸凸凸凹凹啥的一大堆,最終就是一袋東西在那傳,送到你手上。
好了不扯遠(yuǎn),在上面的代碼之后,其實(shí)我們已經(jīng)可以為Controller寫好凸凸來(lái)綁定ViewModel的凹凹,反之亦然
5. Controller與ViewModel的凸凸凹凹綁定
class ViewController: UIViewController
{
let button = UIButton()
let viewModel = ViewModel()
// 按鈕點(diǎn)擊
let buttonTapOutput = PublishRelay<Void>()
// 跳轉(zhuǎn)去設(shè)置支付密碼
let gotoSetpswInput = PublishRelay<Void>()
// 彈密碼輸入框
let showSetpswInput = PublishRelay<Void>()
// 輸入密碼完成
let finishedSettingpswOutput = PublishRelay<String>()
// 流程完成
let allFinishedInput = PublishRelay<Void>()
override func viewDidLoad() {
button.rx.tap
.bind(to: buttonTapOutput)
.disposed(by: rx.disposeBag)
gotoSetpswInput.subscribe(onNext: { (_) in
// 此處添加navigate跳轉(zhuǎn)邏輯
print("跳轉(zhuǎn)啦啦啦")
}).disposed(by: rx.disposeBag)
showSetpswInput.subscribe(onNext: { (_) in
// 此處添加彈出密碼設(shè)置
print("設(shè)置密碼啦啦啦")
}).disposed(by: rx.disposeBag)
allFinishedInput.subscribe(onNext: { (_) in
print("all done")
}).disposed(by: rx.disposeBag)
// Controller <=> ViewModel 綁定
buttonTapOutput
.bind(to: viewModel.checkPswInput)
.disposed(by: rx.disposeBag)
finishedSettingpswOutput
.bind(to: viewModel.setPswFinishInput)
.disposed(by: rx.disposeBag)
viewModel.gotoSetpswOutput.bind(to: gotoSetpswInput).disposed(by: rx.disposeBag)
viewModel.showSetpswOutput.bind(to: showSetpswInput).disposed(by: rx.disposeBag)
viewModel.finishedAllOuput.bind(to: allFinishedInput).disposed(by: rx.disposeBag)
}
}
我們?cè)?code>ViewDidLoad最后添加了點(diǎn)綁定方法,這樣,這個(gè)場(chǎng)景中所有線的凸凸凹凹就全部被結(jié)合到一起了
就這樣,一個(gè)看似復(fù)雜的業(yè)務(wù)流程被解析成了更復(fù)雜的MVVM的Rx形態(tài),不過(guò)是不是在開(kāi)發(fā)思路上順暢了不少呢?大概這就是為什么我們會(huì)用這套東西的原因吧
場(chǎng)景二:添加刪除列表某一項(xiàng)
為什么拿這么簡(jiǎn)單的場(chǎng)景舉例,因?yàn)檫@能很好的體現(xiàn)這套思維的另一個(gè)特征,如果說(shuō)場(chǎng)景一中是帶著你繞圈子,作用是強(qiáng)化一個(gè)個(gè)節(jié)點(diǎn)的概念;那這個(gè)場(chǎng)景就是強(qiáng)調(diào)【數(shù)據(jù)驅(qū)動(dòng)】的概念了
簡(jiǎn)單來(lái)說(shuō)就是你一定希望,我的Array中少一項(xiàng),那以他為數(shù)據(jù)源的tableView就會(huì)自動(dòng)少一行。其實(shí)這個(gè)場(chǎng)景就是幫助我們達(dá)到這個(gè)目的的
所以我們先請(qǐng)我們的數(shù)據(jù)源來(lái)亮相一下:
var sectionedData: BehaviorRelay<[MGItem>]>!
有人會(huì)說(shuō)你作弊,BehaviorRelay又是啥,其實(shí)他就是個(gè)有貨的凸凸凹凹,在前面的PublishRelay中那是個(gè)快遞的角色的話,記住PublishRelay只會(huì)傳遞能量,他不會(huì)留存你的能量,即快遞拿到你的貨物后,給到下一個(gè)之后快遞是不會(huì)保留的你貓糧的,畢竟他不養(yǎng)貓留著也沒(méi)啥用。
但是在這個(gè)場(chǎng)景中,我們需要一個(gè)長(zhǎng)期持有能量的節(jié)點(diǎn)來(lái)充當(dāng)數(shù)據(jù)源,畢竟你的數(shù)據(jù)源如果只是個(gè)快遞小哥,那他可能啥都沒(méi)~,
所以我們用一個(gè)很夸張的例子來(lái)理解BehaviorRelay,把他理解成你善良的母親大人就行了,有一天你把貓糧給你的母親大人,讓她幫你傳達(dá)給你的伙伴,于是這袋貓糧就永遠(yuǎn)留在了你母親大人的心里,雖然最后她會(huì)交給你的伙伴,但是如果這時(shí)候你說(shuō),我還要一袋,我還要兩袋,你母親大人總能幫你變出來(lái),因?yàn)樗呀?jīng)記住了貓糧長(zhǎng)什么樣了,所以你就可以理解,你無(wú)限的索取,你母親大人會(huì)幫你變出一集裝箱的貓糧,而且他們是同一個(gè)樣子的。
專業(yè)點(diǎn)就是,你母親大人不停給你貓糧指針,指向同一個(gè)地址,地址就在她腦子里,所以哪天你說(shuō)我要狗糧的時(shí)候,你母親大人的腦子里就會(huì)立馬變成狗糧,巴不得她把所有貓糧全變成狗糧,雖然現(xiàn)實(shí)中她做不到。
有點(diǎn)啰嗦,所以
BehaviorRelay就是可以永久持有一個(gè)能量,并且你做任何改動(dòng),能量都會(huì)被永久變化的東西,用他來(lái)做數(shù)據(jù)源再好不過(guò)。
1. 還是先從數(shù)據(jù)源定義開(kāi)始
func sectionableData() -> BehaviorRelay<[MGSection<MGItem>]> {
let item1 = MGItem(str: "1")
let item2 = MGItem(str: "2")
let item3 = MGItem(str: "4")
let item4 = MGItem(str: "5")
let section1 = MGSection(header: "header1", items: [item1, item2])
let section2 = MGSection(header: "header2", items: [item3, item4])
return BehaviorRelay(value: [section1, section2])
}
我們用這個(gè)方法來(lái)創(chuàng)建一個(gè)數(shù)據(jù)源,MGSection可能有點(diǎn)陌生,其實(shí)就是我們現(xiàn)在需要的是帶Section的列表,而SectionHeader就是header1,header2。
由于
Rx本身并沒(méi)有MVVM的數(shù)據(jù)驅(qū)動(dòng)的概念,所以我們需要引入RxDataSouces這個(gè)神秘的庫(kù)來(lái)幫我們完善數(shù)據(jù)源,SectionModelType就是RxDataSouces中定義的協(xié)議了
/// MGSection model
public class MGSection<ItemElement>: SectionModelType {
public typealias Item = ItemElement
public var header: String = ""
public var items: [Item] = []
init() {
}
public required init(original: MGSection, items: [Item]) {
self.header = original.header
self.items = items
}
/// 初始化調(diào)用我就行了
///
/// - Parameters:
/// - header: header string
/// - items: items
public convenience init(header: String, items: [Item]) {
let section = MGSection<Item>()
section.header = header
section.items = items
self.init(original: section, items: items)
self.header = header
}
}
其實(shí)看不懂也無(wú)所謂,簡(jiǎn)單理解就是我們創(chuàng)建一個(gè)普通的數(shù)組,然后用RxDataSouces所要求的格式進(jìn)行封裝后得到了BehaviorRelay<[MGSection<MGItem>]>這么一個(gè)數(shù)據(jù)源,僅此而已。
2. Controller的綁定
override func viewDidLoad() {
super.viewDidLoad()
viewModel.initial()
viewModel
.sectionedData.asObservable()
.bind(to: tableView, by: { (_, _, _, item) -> UITableViewCell in
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = item.name
return cell!
}).disposed(by: disposeBag)
}
這樣就看得懂了,雖然這個(gè)bind方法有點(diǎn)讓人無(wú)法理解,畢竟這是個(gè)封裝,內(nèi)容在這里:
func bind<RowItem>(to tableView: UITableView, by configCell : @escaping
(TableViewSectionedDataSource<MGSection<RowItem>>,
UITableView,
IndexPath,
RowItem) -> UITableViewCell )
-> Disposable
where E == DataSourceWithRequest<RowItem> {
let realDataSource = RxTableViewSectionedReloadDataSource<MGSection<RowItem>>(configureCell: configCell)
realDataSource.titleForHeaderInSection = { ds, index in
return ds.sectionModels[index].header
}
return self.bind(to: tableView.rx.items(dataSource: realDataSource))
}
emmm... 怎么說(shuō)呢,這已經(jīng)是我能提供的最能看得懂的版本了,還是那句話,這里不多講
Rx,所以最淺顯的理解就是我們把RxDataSources封裝好的數(shù)據(jù)源bind給了tableView,畢竟bind這個(gè)動(dòng)作之前已經(jīng)見(jiàn)過(guò)了

大概樣子就是這樣,這也太簡(jiǎn)單了,所以接下來(lái)要給
Add Item按鈕搞點(diǎn)事情了
3. 改下數(shù)據(jù)源會(huì)怎么樣
addBar.rx.tap.map{}.subscribe(onNext: { (_) in
print("")
self.viewModel.sectionedData.accept([])
})
我們粗暴的用上面這個(gè)姿勢(shì)來(lái)修改,.accept其實(shí)就是修改了你母親大人腦中的貓糧,當(dāng)然這里是把貓糧吃完了~,而僅僅這一步操作后,其實(shí)tableView就已經(jīng)刷新了,即一個(gè)cell都木有了。
所以這就是我們要達(dá)到的目的,與之前MVC中把數(shù)據(jù)源清空后再tableView.reloadData()的操作相比,這樣更純粹,而之前其實(shí)我們即操作了數(shù)據(jù)源,又刷新了tableview,為了做刪除數(shù)據(jù)這個(gè)操作,我們改了2個(gè)部分的代碼,俗話說(shuō)得好,改的多錯(cuò)的越多~
當(dāng)然有人會(huì)說(shuō)其實(shí)這個(gè)修改數(shù)據(jù)源后
tableview直接變的底層還是這么回事,那我只能說(shuō),畢竟那不是我們自己改的,錯(cuò)的不可能是我們~
當(dāng)然有個(gè)不怎么優(yōu)雅的點(diǎn)大家應(yīng)該可以發(fā)現(xiàn),說(shuō)是說(shuō)清空數(shù)據(jù),其實(shí)我們做的并不是removeAll的操作,而是新建了個(gè)空數(shù)組,換句話說(shuō)這時(shí)候的指令并不是告訴你母親大人請(qǐng)把貓糧吃完,我現(xiàn)在不想要了;而是我們直接給她一個(gè)空袋子說(shuō),你手上的貓糧吃完了,這種看似騙自己的手法確實(shí)有點(diǎn)尷尬,而由于BehaviorRelay中存儲(chǔ)的能量是不可編輯的,所以我們只有通過(guò)覆蓋原本的數(shù)組,來(lái)達(dá)到所謂的刪除目的。
所以如果是一個(gè)添加動(dòng)作,原本是
[1,2],我們只要給個(gè)新數(shù)組[1,2,3],tableView做的其實(shí)就是insertSection動(dòng)作,至于為什么,RxDataSources做了你想知道的一切,這里就不多擴(kuò)展了。
4. MVVM一點(diǎn)呢?
之前提到過(guò)MVVM會(huì)讓簡(jiǎn)單事情復(fù)雜化,所以上面這個(gè)例子明顯并不MVVM,畢竟你怎么可以直接在按鈕點(diǎn)擊事件中去操作ViewModel中的業(yè)務(wù)數(shù)據(jù)呢?說(shuō)好的節(jié)點(diǎn)和快遞呢?
這種來(lái)自靈魂深處的拷問(wèn)讓我們無(wú)法作答,所以還是往下看吧。
addBar.rx.tap.map{}.bind(to: viewModel.addObjInput)
所以我們把Controller改成了這樣,
同時(shí)為了看著美觀,我們給BehaviorRelay加一個(gè)add方法來(lái)欺騙欺騙我們自己:
extension BehaviorRelay
{
func add<T>(element: T) where Element == [T]
{
var newValue = self.value
newValue.append(element)
self.accept(newValue)
}
}
上面提到過(guò)其實(shí)我們是用替換來(lái)充當(dāng)add,所以這里就露骨點(diǎn)這樣寫了,雖然外面調(diào)用的時(shí)候我們可以心安理得的用add了。
所以在ViewModel中,最終節(jié)點(diǎn)訂閱會(huì)變成:
addObjInput.subscribe(onNext: { (_) in
let item5 = MGItem(str: "5")
let item6 = MGItem(str: "6")
let section1 = MGSection(header: "header3", items: [item5, item6])
self.sectionedData.add(element: section1)
})
g結(jié)果就是這樣,你會(huì)問(wèn)既然是add那為什么沒(méi)動(dòng)畫,請(qǐng)相信我,這真的是add,只是我調(diào)了個(gè)沒(méi)動(dòng)畫的RxDatasource的封裝,僅此而已。

至此,你會(huì)發(fā)現(xiàn)我們改了數(shù)據(jù)源,tableView就出現(xiàn)了變化,這就是MVVM的另一個(gè)核心。
場(chǎng)景三:簡(jiǎn)單的不能簡(jiǎn)單的計(jì)數(shù)label

圖中的需求其實(shí)很簡(jiǎn)單,你會(huì)說(shuō)button點(diǎn)擊時(shí)候改變?nèi)肿兞烤托辛耍俳olabel刷新一下就完了,但是你發(fā)現(xiàn)其實(shí)你又操作了數(shù)據(jù),又操作了label,仿佛又有些不太對(duì),
因?yàn)檫@個(gè)場(chǎng)景只是個(gè)鞏固,所以我們就直接貼全部代碼了:
Controller:
class ViewController: UIViewController {
//MARK : - UIs
@IBOutlet weak var countLabel: UILabel!
@IBOutlet weak var countButton: UIButton!
let disposeBag = DisposeBag()
let vm = ViewModel()
//MARK : - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
viewModel.counter.map { "\($0)" }.bind(to: countLabel.rx.text)
countButton.rx.tap.bind(to: viewModel.input)
}
}
ViewModel:
class ViewModel {
let input: PublishRelay<Void> = PublishRelay()
let counter: BehaviorRelay<Int> = BehaviorRelay(value: 1)
init() {
input.map { _ in self.counter.value + 1 }.bind(to: counter)
}
}
同樣我們把所有事情移到了ViewModel,同樣我們省去了刷新Label這么一個(gè)操作,綁定配置之后,我們就可以安心去對(duì)我們的ViewModel做所有業(yè)務(wù)操作了,其實(shí)嚴(yán)格意義上來(lái)說(shuō){ "\($0)" }這個(gè)操作也應(yīng)該在ViewModel中,奈何實(shí)在是不想多寫了,我就寫了個(gè)反面教材以示警戒吧~
至此,簡(jiǎn)單的入門應(yīng)該真的入了,如果還沒(méi)有,那就只能留言作者或者強(qiáng)行懟作者了~