導語
使用RAC實踐購物車邏輯的梳理!首先分析ViewModel
里面的每一個屬性,然后分析ViewModel
里面的每一個方法,再然后就是從ViewController
的頭文件引用中去發現View和Model
的聯系。
心中就有一個疑問,其實總體上來說,ViewModel
終究只是ViewController
和View
和Model
之間的粘連劑量,也就是說,必須先整理出一系列的需求才是通常情況下設置ViewModel
的依據呀,如果沒有需求根本沒法確定ViewModel
,所以首先是發現所有的需求。
整理購物車的思路就是根據MVVM的結構來推導,首先就是推導ViewModel
,這才是一個控制器的核心,因為他直接反應了這個控制器最核心的訴求或者說是需求,只要弄懂ViewModel
,就能抓住控制器的邏輯思路,就能夠分析出完整的邏輯鏈條,問題是,自己的邏輯鏈條是通常都是涉及了多個模塊,需要多個模塊的協作共同完成,因此我要做的就是根據邏輯來梳理MVVM之間的協作關系,而不是將一個完整的邏輯鏈條強制拆成MVVM的四個模塊,這嚴重不能展示出ViewModel的粘連劑的作用。
所以更具需求梳理出邏輯線來,才是最佳的方案。而邏輯鏈條,最好的查看邏輯就是ViewController
的綁定ViewModel
方法了,這里面綁定了什么,就能知道有什么邏輯了。
AddSubView
-
registerNib:[UINib nibWithNibName:]
注冊Xib
類型的Cell
復用
-registerClass:NSClassFromString(@"") forHeaderFooter
注冊sectionHeader
和sectionFooter
設置
dataSource
和delegate
為一個UIService
類對象,這就意味著tableView
的所有回調事件通通都寫到了UIService
類里。唯一需要考慮的問題,就是如何把reloadData
需要的dataArray
數據傳遞到UIService
類,dataArray
作為viewModel
的屬性,如果直接在初始化的時候,令service.dataArray = viewModel.dataArray
,那么假如viewModel
請求數據刷新了viewModel.dataArray
里面的值,然后執行reloadData
方法就會發現service.dataArray
數據源并沒有更新,根本的原因就是service.dataArray
只是記錄了初始化service
時的viewModel.dataArray
值。所以直接在初始化service
時,令service.viewModel = viewModel,
如此一來,無論viewModel.dataArray
變成什么樣,service
總能獲取到最新的dataArray
數據。除此之外,service
里的用戶交互事件也需要viewModel
傳遞出去,典型的cell選中或cell左滑刪除。自定義
ShopCartBarView
,常規的添加結算Button
、全選Button
、價格Lable
拋開不談,重點是ShopCartBarView
如何實時刷新價格Lable
的text
屬性。常規的方法是在將價格Lable
屬性暴露在.h頭文件里或者把想要展示的字符串想方設法傳遞到.m文件里。現在更新價格Lable直接是KVO監聽ShopCartBarView
的money
屬性值的變化,一旦發現self.money
屬性值發生變化,立馬在RACObserve
的subscribeNext
方法里更新價格Lable
。那么,只要ShopCartBarView
的money
屬性值發生改變,立馬就能呈現在價格Lable
上,這相當于添加了一個步驟,原本賦值時通過view.Lable.text
,現在則是通過改變view.money
,后面的self.lable.text = self.money
通過subscribeNext
訂閱RACObserve(self, money)
來實現。view.Lable.text
必須明明白白告訴Lable
需要展示的內容,但是使用view.money
只需要把展示的內容告訴money
屬性存起來,后來的展示具體過程可以通過內部實現,前者是親力親為,后者是下達命令。這兩者對比起來差別還是蠻大的。這樣的好處在于如果僅僅是更新一個
Lable
數據來說,那么確實的,view.Lable.text
還是view.money
這兩個方法差別并不大,真正的區別在于前者有局限性,后者有著更加靈活的處理方式。典型的,如果view.button.enable
也嚴格與money
屬性的值有關,自然第二種方法效果更好,可能單一個邏輯鏈條提現不出來RAC的優勢,但是一旦變成多重連鎖反應,那么毫無疑問,RAC
最有優勢,RAC
也就是為處理多重影響而生。如果不用RAC
,一個money
值變化需要處理后持續兩個邏輯,第一刷新Lable
內容,第二修改Button
狀態。這必然就會想到通過重寫set方法來實現,也能達成目的,但是如果刷新Lable
和修改Button
這兩重邏輯還有先后順序和邏輯依賴呢,這就麻煩了呀!如果一個BOOL狀態的變化承載了一系列連鎖的反應或多重的影響,毫無疑問,
subscribeNext
訂閱RACObserve(self, BOOL)
的信號,然后執行所有的影響和邏輯操作。
數據刷新
直接
self.viewModel
調用方法獲取初始數據并存入viewModel.dataArray
。獲取數據兩種方式,方式一調用getData普通方法,方式二執行
refreshCommand
命令。viewModel.view
刷新UI
業務邏輯
數組遍歷。
rac_sequence
將可變數組變成序列、map
遍歷序列里的每一個元素生成新的序列、array
將序列變成數組、mutableCopy
將普通數組變成可變數組。-
商品全選
第一次初始加載數據的時候,每一個組
section
的選中狀態都是存儲在一個數組之中的。但是這也只是對第一次初始加載時有用,以后如果section
選中狀態的數組被更新,也必須想要把數組里面的section
選中狀態修改后再執行raloadDataSection
方法才有效。subscribeNext
訂閱self.view.button
的點擊事件,self.viewModel
計算商品全選邏輯。商品全選時執行的邏輯包括:重置商品狀態數組、重置大數組的小數組的每一個Model
、計算所有商品的總金額存到self.allPrices
屬性、viewModel.view
刷新UI。需要考慮一種特殊情況,用戶根本不點擊全選按鈕,只是簡單地把所有的商品都勾選一遍,這個時候置全選按鈕于選中狀態?很簡單,
RAC(self.view.button, selected) = RACObserve(self.viewModel,isSelectAll)
相當于button.selected = self.viewModel.isSelectAll,
只要isSelectAll
屬性值發生改變,就發送一個信號,這個賦值的方法就會被調用一次,這其實就間接解決了剛才viewModel.dataArray
數據更新但是UIService.dataArray
依然是初始數據的問題了。這就相當于使用間接的方法來先改變viewModel.isSelectAll
繼而改變viewController.view.button.selected
屬性值。
-
商品刪除
方法一先勾選即將刪除的商品,然后
subscribeNext
訂閱self.view.deleteButton
的點擊事件,self.viewModel
統一計算商品刪除邏輯:1、遍歷dataArray
中的每一個sectionArray
,接著遍歷sectionArray
的每一個商品模型model
,判斷model
選中狀態,如果model
處于選中狀態使用NSMutableIndexSet
集合對象存儲當前model
的index
序號,在sectionArray
遍歷完所有的·model·根據序號集合removeObjects
。這里面考慮一個特殊情況,就是如果刪除一個sectionArray
里面的所有model
,必須從dataArray
中把這個sectionArray
也刪掉。判斷的一句依據就是選中的模型的序號集合的個數等于sectionArray
的元素個數。方法二直接左滑刪除購物車商品,然后在cell的左滑事件里通過self.viewModel計算單個商品的刪除邏輯:獲取indexPath用來定位dataArray中的Model并從dataArray中remove掉、判斷當前section的cell=0決定是reloadData還是reloadSections(
如果reloadData意味著刪除cell組頭選中狀態數組和刪除dataArray中的小sectionArray
)、更新購物車商品數量存到self.count
屬性、調用getAllPrices
方法重新計算購物車總金額并存到self.allPrices
屬性。
-
總共金額
計算總金額,直接相當于把
dataArray
中的沒有商品Model
遍歷一遍。判斷Section
勾選狀態數組元素個數與dataArray
中sectionArray
元素個數的關系,判斷當前是否是全選狀態并存入self.isSelectAll
屬性。遍歷dataArray
中的每一個sectionArray
,接著遍歷sectionArray
中的每一個Model,filter
過濾掉未選中的Model,map
操作每一個處于選中狀態的Model
,計算該商品模型model
的總金額并以@()數組元素的形式返回到數組之中,通過RAC
的array
方法將序列轉變成存滿金額的數組,最后遍歷這個數組對所有元素進行相加,如此得到的購物車被選中所有商品的總共金額并存入self.allPrices
屬性中。總金額實時更新到viewController.view.lable。text屬性上。還是老套路,直接
RAC(vc.view.label, text) = RACObserve(vc.viewModel,allPrices)
。哈哈,錯了,錯的一塌糊涂,這樣根本不行,直接就會導致程序的崩潰,問題的關鍵就是上面的RAC簡寫訂閱方法只適用于綁定BOOL屬性。其它屬性還得是正常的寫法subscribeNext訂閱RACObserve(vc.viewModel,allPrices)屬性值改變的信號。
商品數量。老套路,subscribeNext訂閱
RACObserve(vc.viewModel,counts)
屬性值改變的信號。-
Cell的HeaderView
service作為View的委托,意味著View上所有的參數回調設置和用戶觸發事件都在service對象里,
service
本質上只是幫助viewController
分擔委托代理對調等方法,并不處理用戶觸發事件的邏輯,因此必須通過service.viewModel
將用戶交互事件傳遞到viewModel
中去。headerview.button點擊。1、訂閱headerview.button的點擊事件時,takeUntil跳過
headerView.rac_prepareForReuseSignal
準備復用前的信號,難道是說headerView在rac_prepareForReuseSignal
也要發送信號,headerview.button
的按鈕點擊信號和headerView.rac_prepareForReuseSignal
信號相互干擾,所以必須跳過。2、viewModel
執行headerView.button
點擊事件,傳遞headerView的isSelected
選中狀態和section
序號到viewModel
的方法里,
-
Cell的SubView
cell.selectedButton被點擊:傳遞
cell
的indexPath
和buttotn.selected
狀態到viewModel
的方法里,然后找出indePath
對應的Model
并重置該model的isSelected
屬性,同時判斷的當前sectionArray
中的model
總數和已經被選中的model
進行比較,如果相等,記得修改記錄section是否選中狀態的數組為YES
,表示這一個組的Cell
都是被選中了。然后接下來就是reloadSections
只刷新這一section的數據。同時調用方法重新計算該購物車所有的商品金額保存到viewModel.allPrice
屬性,因為viewController.Lable.text
訂閱了viewModel的allPrice
屬性,因此,只要allPrice被重新賦值,viewController.lable.text
立馬被更新。cell.view.button被點擊,這個時候是在
cell
上添加了一個View
,這個view
上面是兩個按鈕,一個?,一個?,那么自然就需要將這個兩個按鈕事件傳遞到cell
中去,cell拿著這兩個按鈕事件又來對Model
的單個商品數量進行加減,其實沒有這個必要,直接在view
里面對單個商品的已有數量進行加減,直接將加減后的單個商品數量傳遞到Cell里面就可以了嘛!將單個商品的數量傳遞到cell的方式包括:第一通過Block
傳遞,每當訂閱到按鈕事件的信號后,就將保存的商品數量進行加減,加減完成后,通過Block
將加減后的單個商品數量傳遞到cell里面。第二就是在cell里面通過RACObserve(view,number)
訂閱view.number
屬性值的變化,一旦發生變化,就進入通過viewModel
調用方法處理單個商品數量變化的邏輯。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)
的值信號,這個是值就是當前TextField
的最新字符串。方式二是subscribeNext
訂閱[NSNotificationCenter defaultCenter] rac_addObserverForName:@"UITextFieldTextDidEndEditingNotification" object:TextField]
的信號,只有當TextField結束編輯之后才會發送TextField
這個UI
控件為值的信號,在cell.view
里訂閱到這個信號之后,判斷TextField.text
與總庫存的關系,如果大于總共的庫存,直接Block
返回單個商品的庫存數量,其它數量,直接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的單個商品數量同時保存在cell.view.number
屬性里,因為數據的刷新還得是依靠Cell
的reloadData
。單個商品數量變化的深層次邏輯。當單個商品數量通過
Block
回調到Cell
之后,cell是不能負責因為當個商品數量變化引起的控制器邏輯變化,必須繼續把當個商品數量和Cell
的IndexPath
以參數的形式傳遞到ViewModel
中進行處理。邏輯包括:根據IndexPath
定位Cell
的Model
,修改重置Model的單個商品數量屬性,修改模型后就,可以reloadSection
改組的所有Cell
數據,最后重新計算當前所有被選中商品的總金額。