電商購物(MVVM+ReactiveCocoa)

導語

使用RAC實踐購物車邏輯的梳理!首先分析ViewModel里面的每一個屬性,然后分析ViewModel里面的每一個方法,再然后就是從ViewController的頭文件引用中去發現View和Model的聯系。

心中就有一個疑問,其實總體上來說,ViewModel終究只是ViewControllerViewModel之間的粘連劑量,也就是說,必須先整理出一系列的需求才是通常情況下設置ViewModel的依據呀,如果沒有需求根本沒法確定ViewModel,所以首先是發現所有的需求。

整理購物車的思路就是根據MVVM的結構來推導,首先就是推導ViewModel,這才是一個控制器的核心,因為他直接反應了這個控制器最核心的訴求或者說是需求,只要弄懂ViewModel,就能抓住控制器的邏輯思路,就能夠分析出完整的邏輯鏈條,問題是,自己的邏輯鏈條是通常都是涉及了多個模塊,需要多個模塊的協作共同完成,因此我要做的就是根據邏輯來梳理MVVM之間的協作關系,而不是將一個完整的邏輯鏈條強制拆成MVVM的四個模塊,這嚴重不能展示出ViewModel的粘連劑的作用。

所以更具需求梳理出邏輯線來,才是最佳的方案。而邏輯鏈條,最好的查看邏輯就是ViewController的綁定ViewModel方法了,這里面綁定了什么,就能知道有什么邏輯了。

AddSubView

  • registerNib:[UINib nibWithNibName:]注冊Xib類型的Cell復用

-registerClass:NSClassFromString(@"") forHeaderFooter注冊sectionHeadersectionFooter

  • 設置dataSourcedelegate為一個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如何實時刷新價格Labletext屬性。常規的方法是在將價格Lable屬性暴露在.h頭文件里或者把想要展示的字符串想方設法傳遞到.m文件里。現在更新價格Lable直接是KVO監聽ShopCartBarViewmoney屬性值的變化,一旦發現self.money屬性值發生變化,立馬在RACObservesubscribeNext方法里更新價格Lable。那么,只要ShopCartBarViewmoney屬性值發生改變,立馬就能呈現在價格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集合對象存儲當前modelindex序號,在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勾選狀態數組元素個數與dataArraysectionArray元素個數的關系,判斷當前是否是全選狀態并存入self.isSelectAll屬性。遍歷dataArray中的每一個sectionArray,接著遍歷sectionArray中的每一個Model,filter過濾掉未選中的Model,map操作每一個處于選中狀態的Model,計算該商品模型model的總金額并以@()數組元素的形式返回到數組之中,通過RACarray方法將序列轉變成存滿金額的數組,最后遍歷這個數組對所有元素進行相加,如此得到的購物車被選中所有商品的總共金額并存入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被點擊:傳遞cellindexPathbuttotn.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_textSignalTextField開始編輯開始,只要字符變化一下,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屬性值與Colorbutton.selected的綁定,這都是套路,訂閱RACObserve(self, number)中number屬性值的變化,減號button.selected = @(number>1),加號button.selected = @(number<庫存)RAC(TextField,textColor) = 庫存?[UIColor blackColor]:[UIColor redColor];最后,有很重要的一點就是需要將Block回掉到cell的單個商品數量同時保存在cell.view.number屬性里,因為數據的刷新還得是依靠CellreloadData

    • 單個商品數量變化的深層次邏輯。當單個商品數量通過Block回調到Cell之后,cell是不能負責因為當個商品數量變化引起的控制器邏輯變化,必須繼續把當個商品數量和CellIndexPath以參數的形式傳遞到ViewModel中進行處理。邏輯包括:根據IndexPath定位CellModel,修改重置Model的單個商品數量屬性,修改模型后就,可以reloadSection改組的所有Cell數據,最后重新計算當前所有被選中商品的總金額。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,165評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,595評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,435評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,943評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,175評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,713評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,599評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,788評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,034評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,408評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,747評論 2 370

推薦閱讀更多精彩內容