电商购物(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阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容