電商購物(MVVM+ReactiveCocoa)

導(dǎo)語

使用RAC實踐購物車邏輯的梳理眷细!首先分析ViewModel里面的每一個屬性棋返,然后分析ViewModel里面的每一個方法,再然后就是從ViewController的頭文件引用中去發(fā)現(xiàn)View和Model的聯(lián)系。

心中就有一個疑問,其實總體上來說译打,ViewModel終究只是ViewControllerViewModel之間的粘連劑量,也就是說拇颅,必須先整理出一系列的需求才是通常情況下設(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注冊sectionHeadersectionFooter

  • 設(shè)置dataSourcedelegate為一個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如何實時刷新價格Labletext屬性爆价。常規(guī)的方法是在將價格Lable屬性暴露在.h頭文件里或者把想要展示的字符串想方設(shè)法傳遞到.m文件里。現(xiàn)在更新價格Lable直接是KVO監(jiān)聽ShopCartBarViewmoney屬性值的變化媳搪,一旦發(fā)現(xiàn)self.money屬性值發(fā)生變化铭段,立馬在RACObservesubscribeNext方法里更新價格Lable。那么秦爆,只要ShopCartBarViewmoney屬性值發(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)前modelindex序號歪今,在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ù)與dataArraysectionArray元素個數(shù)的關(guān)系滔金,判斷當(dāng)前是否是全選狀態(tài)并存入self.isSelectAll屬性。遍歷dataArray中的每一個sectionArray,接著遍歷sectionArray中的每一個Model,filter過濾掉未選中的Model,map操作每一個處于選中狀態(tài)的Model,計算該商品模型model的總金額并以@()數(shù)組元素的形式返回到數(shù)組之中茂嗓,通過RACarray方法將序列轉(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被點擊:傳遞cellindexPathbuttotn.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_textSignalTextField開始編輯開始,只要字符變化一下兔毒,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屬性值與Colorbutton.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ù)的刷新還得是依靠CellreloadData荧嵌。

    • 單個商品數(shù)量變化的深層次邏輯停士。當(dāng)單個商品數(shù)量通過Block回調(diào)到Cell之后,cell是不能負(fù)責(zé)因為當(dāng)個商品數(shù)量變化引起的控制器邏輯變化完丽,必須繼續(xù)把當(dāng)個商品數(shù)量和CellIndexPath以參數(shù)的形式傳遞到ViewModel中進行處理恋技。邏輯包括:根據(jù)IndexPath定位CellModel,修改重置Model的單個商品數(shù)量屬性,修改模型后就逻族,可以reloadSection改組的所有Cell數(shù)據(jù)蜻底,最后重新計算當(dāng)前所有被選中商品的總金額。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末聘鳞,一起剝皮案震驚了整個濱河市薄辅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抠璃,老刑警劉巖站楚,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異搏嗡,居然都是意外死亡窿春,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門采盒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旧乞,“玉大人,你說我怎么就攤上這事磅氨〕咂埽” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵烦租,是天一觀的道長延赌。 經(jīng)常有香客問我,道長叉橱,這世上最難降的妖魔是什么挫以? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮赏迟,結(jié)果婚禮上屡贺,老公的妹妹穿的比我還像新娘蠢棱。我一直安慰自己锌杀,他們只是感情好甩栈,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著糕再,像睡著了一般量没。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上突想,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天殴蹄,我揣著相機與錄音,去河邊找鬼猾担。 笑死袭灯,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绑嘹。 我是一名探鬼主播稽荧,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼工腋!你這毒婦竟也來了姨丈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤擅腰,失蹤者是張志新(化名)和其女友劉穎蟋恬,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趁冈,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡歼争,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了渗勘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矾飞。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖呀邢,靈堂內(nèi)的尸體忽然破棺而出洒沦,到底是詐尸還是另有隱情,我是刑警寧澤价淌,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布申眼,位于F島的核電站,受9級特大地震影響蝉衣,放射性物質(zhì)發(fā)生泄漏括尸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一病毡、第九天 我趴在偏房一處隱蔽的房頂上張望濒翻。 院中可真熱鬧,春花似錦、人聲如沸有送。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雀摘。三九已至裸删,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間阵赠,已是汗流浹背涯塔。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留清蚀,地道東北人匕荸。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像枷邪,于是被迫代替她去往敵國和親每聪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容