導(dǎo)語
使用RAC實踐購物車邏輯的梳理眷细!首先分析ViewModel
里面的每一個屬性棋返,然后分析ViewModel
里面的每一個方法,再然后就是從ViewController
的頭文件引用中去發(fā)現(xiàn)View和Model
的聯(lián)系。
心中就有一個疑問,其實總體上來說译打,ViewModel
終究只是ViewController
和View
和Model
之間的粘連劑量,也就是說拇颅,必須先整理出一系列的需求才是通常情況下設(shè)置ViewModel
的依據(jù)呀奏司,如果沒有需求根本沒法確定ViewModel
,所以首先是發(fā)現(xiàn)所有的需求樟插。
整理購物車的思路就是根據(jù)MVVM的結(jié)構(gòu)來推導(dǎo)韵洋,首先就是推導(dǎo)ViewModel
,這才是一個控制器的核心岸夯,因為他直接反應(yīng)了這個控制器最核心的訴求或者說是需求,只要弄懂ViewModel
们妥,就能抓住控制器的邏輯思路猜扮,就能夠分析出完整的邏輯鏈條,問題是监婶,自己的邏輯鏈條是通常都是涉及了多個模塊旅赢,需要多個模塊的協(xié)作共同完成,因此我要做的就是根據(jù)邏輯來梳理MVVM之間的協(xié)作關(guān)系惑惶,而不是將一個完整的邏輯鏈條強制拆成MVVM的四個模塊煮盼,這嚴(yán)重不能展示出ViewModel的粘連劑的作用。
所以更具需求梳理出邏輯線來带污,才是最佳的方案僵控。而邏輯鏈條,最好的查看邏輯就是ViewController
的綁定ViewModel
方法了鱼冀,這里面綁定了什么报破,就能知道有什么邏輯了。
AddSubView
-
registerNib:[UINib nibWithNibName:]
注冊Xib
類型的Cell
復(fù)用
-registerClass:NSClassFromString(@"") forHeaderFooter
注冊sectionHeader
和sectionFooter
設(shè)置
dataSource
和delegate
為一個UIService
類對象千绪,這就意味著tableView
的所有回調(diào)事件通通都寫到了UIService
類里充易。唯一需要考慮的問題,就是如何把reloadData
需要的dataArray
數(shù)據(jù)傳遞到UIService
類荸型,dataArray
作為viewModel
的屬性盹靴,如果直接在初始化的時候,令service.dataArray = viewModel.dataArray
,那么假如viewModel
請求數(shù)據(jù)刷新了viewModel.dataArray
里面的值,然后執(zhí)行reloadData
方法就會發(fā)現(xiàn)service.dataArray
數(shù)據(jù)源并沒有更新稿静,根本的原因就是service.dataArray
只是記錄了初始化service
時的viewModel.dataArray
值梭冠。所以直接在初始化service
時,令service.viewModel = viewModel,
如此一來自赔,無論viewModel.dataArray
變成什么樣妈嘹,service
總能獲取到最新的dataArray
數(shù)據(jù)。除此之外绍妨,service
里的用戶交互事件也需要viewModel
傳遞出去润脸,典型的cell選中或cell左滑刪除。自定義
ShopCartBarView
他去,常規(guī)的添加結(jié)算Button
毙驯、全選Button
、價格Lable
拋開不談灾测,重點是ShopCartBarView
如何實時刷新價格Lable
的text
屬性爆价。常規(guī)的方法是在將價格Lable
屬性暴露在.h頭文件里或者把想要展示的字符串想方設(shè)法傳遞到.m文件里。現(xiàn)在更新價格Lable直接是KVO監(jiān)聽ShopCartBarView
的money
屬性值的變化媳搪,一旦發(fā)現(xiàn)self.money
屬性值發(fā)生變化铭段,立馬在RACObserve
的subscribeNext
方法里更新價格Lable
。那么秦爆,只要ShopCartBarView
的money
屬性值發(fā)生改變序愚,立馬就能呈現(xiàn)在價格Lable
上,這相當(dāng)于添加了一個步驟等限,原本賦值時通過view.Lable.text
爸吮,現(xiàn)在則是通過改變view.money
,后面的self.lable.text = self.money
通過subscribeNext
訂閱RACObserve(self, money)
來實現(xiàn)。view.Lable.text
必須明明白白告訴Lable
需要展示的內(nèi)容望门,但是使用view.money
只需要把展示的內(nèi)容告訴money
屬性存起來形娇,后來的展示具體過程可以通過內(nèi)部實現(xiàn),前者是親力親為筹误,后者是下達命令桐早。這兩者對比起來差別還是蠻大的。這樣的好處在于如果僅僅是更新一個
Lable
數(shù)據(jù)來說厨剪,那么確實的勘畔,view.Lable.text
還是view.money
這兩個方法差別并不大,真正的區(qū)別在于前者有局限性丽惶,后者有著更加靈活的處理方式炫七。典型的,如果view.button.enable
也嚴(yán)格與money
屬性的值有關(guān)钾唬,自然第二種方法效果更好万哪,可能單一個邏輯鏈條提現(xiàn)不出來RAC的優(yōu)勢侠驯,但是一旦變成多重連鎖反應(yīng),那么毫無疑問奕巍,RAC
最有優(yōu)勢吟策,RAC
也就是為處理多重影響而生。如果不用RAC
,一個money
值變化需要處理后持續(xù)兩個邏輯的止,第一刷新Lable
內(nèi)容,第二修改Button
狀態(tài)檩坚。這必然就會想到通過重寫set方法來實現(xiàn),也能達成目的诅福,但是如果刷新Lable
和修改Button
這兩重邏輯還有先后順序和邏輯依賴呢匾委,這就麻煩了呀!如果一個BOOL狀態(tài)的變化承載了一系列連鎖的反應(yīng)或多重的影響,毫無疑問氓润,
subscribeNext
訂閱RACObserve(self, BOOL)
的信號赂乐,然后執(zhí)行所有的影響和邏輯操作。
數(shù)據(jù)刷新
直接
self.viewModel
調(diào)用方法獲取初始數(shù)據(jù)并存入viewModel.dataArray
咖气。獲取數(shù)據(jù)兩種方式挨措,方式一調(diào)用getData普通方法,方式二執(zhí)行
refreshCommand
命令崩溪。viewModel.view
刷新UI
業(yè)務(wù)邏輯
數(shù)組遍歷浅役。
rac_sequence
將可變數(shù)組變成序列、map
遍歷序列里的每一個元素生成新的序列伶唯、array
將序列變成數(shù)組觉既、mutableCopy
將普通數(shù)組變成可變數(shù)組。-
商品全選
第一次初始加載數(shù)據(jù)的時候抵怎,每一個組
section
的選中狀態(tài)都是存儲在一個數(shù)組之中的奋救。但是這也只是對第一次初始加載時有用岭参,以后如果section
選中狀態(tài)的數(shù)組被更新反惕,也必須想要把數(shù)組里面的section
選中狀態(tài)修改后再執(zhí)行raloadDataSection
方法才有效。subscribeNext
訂閱self.view.button
的點擊事件演侯,self.viewModel
計算商品全選邏輯姿染。商品全選時執(zhí)行的邏輯包括:重置商品狀態(tài)數(shù)組、重置大數(shù)組的小數(shù)組的每一個Model
秒际、計算所有商品的總金額存到self.allPrices
屬性悬赏、viewModel.view
刷新UI。需要考慮一種特殊情況娄徊,用戶根本不點擊全選按鈕闽颇,只是簡單地把所有的商品都勾選一遍,這個時候置全選按鈕于選中狀態(tài)寄锐?很簡單兵多,
RAC(self.view.button尖啡, selected) = RACObserve(self.viewModel,isSelectAll)
相當(dāng)于button.selected = self.viewModel.isSelectAll,
只要isSelectAll
屬性值發(fā)生改變剩膘,就發(fā)送一個信號衅斩,這個賦值的方法就會被調(diào)用一次,這其實就間接解決了剛才viewModel.dataArray
數(shù)據(jù)更新但是UIService.dataArray
依然是初始數(shù)據(jù)的問題了怠褐。這就相當(dāng)于使用間接的方法來先改變viewModel.isSelectAll
繼而改變viewController.view.button.selected
屬性值畏梆。
-
商品刪除
方法一先勾選即將刪除的商品,然后
subscribeNext
訂閱self.view.deleteButton
的點擊事件奈懒,self.viewModel
統(tǒng)一計算商品刪除邏輯:1奠涌、遍歷dataArray
中的每一個sectionArray
,接著遍歷sectionArray
的每一個商品模型model
,判斷model
選中狀態(tài)瑰枫,如果model
處于選中狀態(tài)使用NSMutableIndexSet
集合對象存儲當(dāng)前model
的index
序號歪今,在sectionArray
遍歷完所有的·model·根據(jù)序號集合removeObjects
。這里面考慮一個特殊情況喊熟,就是如果刪除一個sectionArray
里面的所有model
茴丰,必須從dataArray
中把這個sectionArray
也刪掉达皿。判斷的一句依據(jù)就是選中的模型的序號集合的個數(shù)等于sectionArray
的元素個數(shù)。方法二直接左滑刪除購物車商品贿肩,然后在cell的左滑事件里通過self.viewModel計算單個商品的刪除邏輯:獲取indexPath用來定位dataArray中的Model并從dataArray中remove掉峦椰、判斷當(dāng)前section的cell=0決定是reloadData還是reloadSections(
如果reloadData意味著刪除cell組頭選中狀態(tài)數(shù)組和刪除dataArray中的小sectionArray
)、更新購物車商品數(shù)量存到self.count
屬性汰规、調(diào)用getAllPrices
方法重新計算購物車總金額并存到self.allPrices
屬性汤功。
-
總共金額
計算總金額,直接相當(dāng)于把
dataArray
中的沒有商品Model
遍歷一遍溜哮。判斷Section
勾選狀態(tài)數(shù)組元素個數(shù)與dataArray
中sectionArray
元素個數(shù)的關(guān)系滔金,判斷當(dāng)前是否是全選狀態(tài)并存入self.isSelectAll
屬性。遍歷dataArray
中的每一個sectionArray
,接著遍歷sectionArray
中的每一個Model,filter
過濾掉未選中的Model,map
操作每一個處于選中狀態(tài)的Model
,計算該商品模型model
的總金額并以@()數(shù)組元素的形式返回到數(shù)組之中茂嗓,通過RAC
的array
方法將序列轉(zhuǎn)變成存滿金額的數(shù)組餐茵,最后遍歷這個數(shù)組對所有元素進行相加,如此得到的購物車被選中所有商品的總共金額并存入self.allPrices
屬性中述吸。總金額實時更新到viewController.view.lable忿族。text屬性上。還是老套路蝌矛,直接
RAC(vc.view.label道批, text) = RACObserve(vc.viewModel,allPrices)
入撒。哈哈隆豹,錯了,錯的一塌糊涂茅逮,這樣根本不行璃赡,直接就會導(dǎo)致程序的崩潰簿煌,問題的關(guān)鍵就是上面的RAC簡寫訂閱方法只適用于綁定BOOL屬性。其它屬性還得是正常的寫法subscribeNext訂閱RACObserve(vc.viewModel鉴吹,allPrices)屬性值改變的信號姨伟。
商品數(shù)量。老套路豆励,subscribeNext訂閱
RACObserve(vc.viewModel夺荒,counts)
屬性值改變的信號。-
Cell的HeaderView
service作為View的委托良蒸,意味著View上所有的參數(shù)回調(diào)設(shè)置和用戶觸發(fā)事件都在service對象里技扼,
service
本質(zhì)上只是幫助viewController
分擔(dān)委托代理對調(diào)等方法,并不處理用戶觸發(fā)事件的邏輯嫩痰,因此必須通過service.viewModel
將用戶交互事件傳遞到viewModel
中去剿吻。headerview.button點擊。1串纺、訂閱headerview.button的點擊事件時丽旅,takeUntil跳過
headerView.rac_prepareForReuseSignal
準(zhǔn)備復(fù)用前的信號,難道是說headerView在rac_prepareForReuseSignal
也要發(fā)送信號纺棺,headerview.button
的按鈕點擊信號和headerView.rac_prepareForReuseSignal
信號相互干擾榄笙,所以必須跳過。2祷蝌、viewModel
執(zhí)行headerView.button
點擊事件茅撞,傳遞headerView的isSelected
選中狀態(tài)和section
序號到viewModel
的方法里,
-
Cell的SubView
cell.selectedButton被點擊:傳遞
cell
的indexPath
和buttotn.selected
狀態(tài)到viewModel
的方法里巨朦,然后找出indePath
對應(yīng)的Model
并重置該model的isSelected
屬性米丘,同時判斷的當(dāng)前sectionArray
中的model
總數(shù)和已經(jīng)被選中的model
進行比較,如果相等糊啡,記得修改記錄section是否選中狀態(tài)的數(shù)組為YES
拄查,表示這一個組的Cell
都是被選中了。然后接下來就是reloadSections
只刷新這一section的數(shù)據(jù)悔橄。同時調(diào)用方法重新計算該購物車所有的商品金額保存到viewModel.allPrice
屬性靶累,因為viewController.Lable.text
訂閱了viewModel的allPrice
屬性腺毫,因此癣疟,只要allPrice被重新賦值,viewController.lable.text
立馬被更新潮酒。cell.view.button被點擊睛挚,這個時候是在
cell
上添加了一個View
,這個view
上面是兩個按鈕,一個?急黎,一個?扎狱,那么自然就需要將這個兩個按鈕事件傳遞到cell
中去侧到,cell拿著這兩個按鈕事件又來對Model
的單個商品數(shù)量進行加減,其實沒有這個必要淤击,直接在view
里面對單個商品的已有數(shù)量進行加減匠抗,直接將加減后的單個商品數(shù)量傳遞到Cell里面就可以了嘛!將單個商品的數(shù)量傳遞到cell的方式包括:第一通過Block
傳遞污抬,每當(dāng)訂閱到按鈕事件的信號后汞贸,就將保存的商品數(shù)量進行加減,加減完成后印机,通過Block
將加減后的單個商品數(shù)量傳遞到cell里面矢腻。第二就是在cell里面通過RACObserve(view,number)
訂閱view.number
屬性值的變化射赛,一旦發(fā)生變化多柑,就進入通過viewModel
調(diào)用方法處理單個商品數(shù)量變化的邏輯。cell.view.textfield被編輯楣责。這就是一個特別好的示例了竣灌,
cell
上面添加了View,View
上面不僅添加了Button
,更添加了TextField
秆麸,無論是Button
還是TextField
都需要進行交互帐偎,然后通過viewModel
處理被點擊還是被編輯所帶來的深層次影響。文本框的RAC
處理蛔屹。兩種方式削樊,方式一通過RAC(viewController.viewModel, password) = TextField.rac_textSignal
從TextField
開始編輯開始,只要字符變化一下兔毒,viewModel
就會立即subscribeNext
訂閱到RACObserve(self漫贞,password)
的值信號,這個是值就是當(dāng)前TextField
的最新字符串育叁。方式二是subscribeNext
訂閱[NSNotificationCenter defaultCenter] rac_addObserverForName:@"UITextFieldTextDidEndEditingNotification" object:TextField]
的信號迅脐,只有當(dāng)TextField結(jié)束編輯之后才會發(fā)送TextField
這個UI
控件為值的信號,在cell.view
里訂閱到這個信號之后豪嗽,判斷TextField.text
與總庫存的關(guān)系谴蔑,如果大于總共的庫存,直接Block
返回單個商品的庫存數(shù)量龟梦,其它數(shù)量隐锭,直接Block返回就行。一談到TextField
必然會涉及到text屬性值與Color
和button.selected
的綁定计贰,這都是套路钦睡,訂閱RACObserve(self, number)中number
屬性值的變化,減號button.selected = @(number>1)
躁倒,加號button.selected = @(number<庫存)
荞怒。RAC(TextField洒琢,textColor) = 庫存?[UIColor blackColor]:[UIColor redColor]褐桌;
最后衰抑,有很重要的一點就是需要將Block
回掉到cell的單個商品數(shù)量同時保存在cell.view.number
屬性里,因為數(shù)據(jù)的刷新還得是依靠Cell
的reloadData
荧嵌。單個商品數(shù)量變化的深層次邏輯停士。當(dāng)單個商品數(shù)量通過
Block
回調(diào)到Cell
之后,cell是不能負(fù)責(zé)因為當(dāng)個商品數(shù)量變化引起的控制器邏輯變化完丽,必須繼續(xù)把當(dāng)個商品數(shù)量和Cell
的IndexPath
以參數(shù)的形式傳遞到ViewModel
中進行處理恋技。邏輯包括:根據(jù)IndexPath
定位Cell
的Model
,修改重置Model的單個商品數(shù)量屬性,修改模型后就逻族,可以reloadSection
改組的所有Cell
數(shù)據(jù)蜻底,最后重新計算當(dāng)前所有被選中商品的總金額。