一:为什么要用MVVM?
为什么要用MVVM?只是因为它不会让我时常懵逼。
每次做完项目过后,都会被自己庞大的ViewController代码吓坏,不管是什么网络请求、networking data process、跳转交互逻辑统统往ViewController里面塞,就算是自己写的代码,也不敢直视。我不得不思考是不是MVC模式太过落后了,毕竟它叫做Massive View Controller,其实说MVC落后不太合理,说它太原生了比较合适。
MVC模式的历史非常的久远,它其实不过是对编程模式的一种模块化,不管是MVVM、MVCS、还是听起来就毛骨悚然的VIPER,都是对MVC标准的三个模块的继续划分,细分下去,使每个模块的功能更加的独立和单一,而最终目的都是为了提升代码的规范程度,解耦,和降低维护成本。具体用什么模式需要根据项目的需求来决定,而这里,我简单的说说自己对MVVM架构的理解和设计思想,浅谈拙见。
二:MVVM模块划分
传统的MVC模式分为:Model、View、Controller。Model是数据模型,有胖瘦之分,View负责界面展示,而Controller就负责剩下的逻辑和业务,瞬间Controller心中一万个*奔腾而过。
MVVM模式只是多了一个ViewModel,它的作用是为Controller减负,将Controller里面的逻辑(主要是弱业务逻辑)转移到自身,其实它涉及到的工作不止是这些,还包括页面展示数据的处理等。(后序章节会有具体讲解)
我的设计是这样的:
- 一个View对应一个ViewModel,View界面元素属性与ViewModel处理后的数据属性绑定
- Model只是在有网络数据的时候需要创建,它的作用只是一个数据的中专站,也就是一个极为简介的瘦model
- 这里弱化了Model的作用,而将对网络数据的处理的逻辑放在ViewModel中,也就是说,只有在有网络数据展示的View的ViewModel中,才会看见Model的影子,而处理过后的数据,将变成ViewModel的属性,注意一点,这些属性一定要尽量“直观”,比如能写成UIImage就不要写成URL
- ViewModel和Model可以视情况看是否需要属性绑定
- Controller的作用就是将主View通过与之对应的ViewModel初始化,然后添加到self.view,然后就是监听跳转逻辑触发等少部分业务逻辑,当然,ViewController的跳转还是需要在这里实现。 注意:这里面提到的绑定,其实就是对属性的监听,当属性变化时,监听者做一些逻辑处理,强大的框架来了————RAC
三:ReactiveCocoa
RAC是一个强大的工具,它和MVVM模式的结合使用只能用一个词形容————完美。
当然,有些开发者不太愿意用这些东西,大概是因为他们觉得这破坏了代理、通知、监听、block等的复杂逻辑观感,但是我在这里大力推崇RAC,因为我的MVVM搭建思路里面会涉及大量的属性绑定、事件传递,我可不想写上一万个协议来实现这些简单的功能,运用RAC能大量简化代码,使逻辑更加的清晰。
接下来我将对我的MVVM架构实现思路做一个详细的讲解,在这之前,如果你没有用过RAC,请先移步:
大致的了解一下RAC过后,便可以往下(^)
四:MVVM模块具体实现
这是要实现的界面:
AF45BFF3B07B52D222AF90AE1CCBAC18.png
1、Model
这里我弱化了Model的作用,它只是作为一个网络请求数据的中转站,只有在View需要显示网络数据的时候,对应的ViewModel里面才有Model的相关处理。
2、ViewModel
在实际开发当中,一个View对应一个ViewModel,主View对应并且绑定一个主ViewModel。
主ViewModel承担了网络请求、点击事件协议、初始化子ViewModel并且给子ViewModel的属性赋初值;网络请求成功返回数据过后,主ViewModel还需要给子ViewModel的属性赋予新的值。
主ViewModel的观感是这样的:
@interface MineViewModel : NSObject //viewModel @property (nonatomic, strong) MineHeaderViewModel *mineHeaderViewModel; @property (nonatomic, strong) NSArray<MineTopCollectionViewCellViewModel *> *dataSorceOfMineTopCollectionViewCell; @property (nonatomic, strong) NSArray<MineDownCollectionViewCellViewModel *> *dataSorceOfMineDownCollectionViewCell; //RACCommand @property (nonatomic, strong) RACCommand *autoLoginCommand; //RACSubject @property (nonatomic, strong) RACSubject *pushSubject; @end
其中,RACCommand是放网络请求的地方,RACSubject相当于协议,这里用于点击事件的代理,而ViewModel下面的一个ViewModel属性和三个装有ViewModel的数组我需要着重说一下。
在iOS开发中,我们通常会自定义View,而自定义的View有可能是继承自UICollectionviewCell(UITableViewCell、UITableViewHeaderFooterView等),当我们自定义一个View的时候,这个View不需要复用且只有一个,我们就在主ViewModel声明一个子ViewModel属性,当我们自定义一个需要复用的cell、item、headerView等的时候,我们就在主ViewModel中声明数组属性,用于储存复用的cell、item的ViewModel,中心思想仍然是一个View对应一个ViewModel。
在.m文件中,对这些属性做懒加载处理,并且将RACCommand和RACSubject配置好,方便之后在需要的时候触发以及调用,代码如下:
@implementation MineViewModel
- (instancetype)init { self = [super init]; if (self) { [self initialize]; } return self; } - (void)initialize { [self.autoLoginCommand.executionSignals.switchToLatest subscribeNext:^(id responds) { //处理网络请求数据 ...... }]; } #pragma mark *** getter *** - (RACSubject *)pushSubject { if (!_pushSubject) { _pushSubject = [RACSubject subject]; } return _pushSubject; } - (RACCommand *)autoLoginCommand { if (!_autoLoginCommand) { _autoLoginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSDictionary *paramDic = @{......}; [Network start:paramDic success:^(id datas) { [subscriber sendNext:datas]; [subscriber sendCompleted]; } failure:^(NSString *errorMsg) { [subscriber sendNext:errorMsg]; [subscriber sendCompleted]; }]; return nil; }]; }]; } return _autoLoginCommand; } - (MineHeaderViewModel *)mineHeaderViewModel { if (!_mineHeaderViewModel) { _mineHeaderViewModel = [MineHeaderViewModel new]; _mineHeaderViewModel.headerBackgroundImage = [UIImage imageNamed:@"BG"]; _mineHeaderViewModel.headerImageUrlStr = nil; [[[RACObserve([LoginBackInfoModel shareLoginBackInfoModel], headimg) distinctUntilChanged] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id x) { if (x == nil) { _mineHeaderViewModel.headerImageUrlStr = nil; } else { _mineHeaderViewModel.headerImageUrlStr = x; } }]; ...... return _mineHeaderViewModel; } - (NSArray<MineTopCollectionViewCellViewModel *> *)dataSorceOfMineTopCollectionViewCell { if (!_dataSorceOfMineTopCollectionViewCell) { MineTopCollectionViewCellViewModel *model1 = [MineTopCollectionViewCellViewModel new]; MineTopCollectionViewCellViewModel *model2 = [MineTopCollectionViewCellViewModel new]; ...... _dataSorceOfMineTopCollectionViewCell = @[model1, model2]; } return _dataSorceOfMineTopCollectionViewCell; } - (NSArray