移动端架构上的设计,本质上都是三个角色:数据结构、数据流管理、数据展示。
不管是MVC、MVVM、MVP、VIPER或者任何新的设计模式、都跳不出这三个角色。无非是把数据管理者的工作进行拆分、唯一的界定标准就是把工作拆分的粒度大小。
而无论哪种思想、最终都逃不开三个问题的《《取舍》》。代码量、通用性、可读性。
MVC
MVC就是典型的着重通用型与可读性、这正是一个作为万物之初的架构所需要保证的事。简单、易学。
架构模型
- Model:数据结构,数据映射
- View:数据展示
- Controller:负责根据需求对Model及View间的数据流调配
Controller的瘦身
不过和广义的MVC不同,iOS端由于UIViewController自带一个容器View。所以除了上述的正统任务之外,Controller还需要承担View的生成,布局等的任务。Controller实际上是Controller+View的一个角色,这样的设定也导致了C层过于臃肿的问题
胖Model
主旨是Controller从Model里拿到的数据、不需要进行更多的判断、处理等操作、就能使用。缺点是移植困难、复用困难
1
2
3
4
5
6
7
8
9
10
11Json Data:
timestamp:1234567
FatModel:
@property (nonatomic, assign) CGFloat timestamp;
- (NSString *)ymdDateString; // 2015-04-20 15:16
- (NSString *)standardString; // 3分钟前、1小时前、一天前
Controller:
self.dateLabel.text = [FatModelIns ymdDateString];
self.gapLabel.text = [FatModelIns standardString];瘦Model
就是要把MVC的M贯彻倒底、除了业务的表达啥都不管。所以瘦Model要借助一些外来的辅助模块(索性可以叫Helper、Util)来对弱业务做抽象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Json Data:
{
"name":"casa",
"sex":"male",
}
SlimModel:
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *sex;
Util:
#define Male 1;
#define Female 0;
+ (BOOL)sexWithString:(NSString *)sex;
Controller:
if ([Helper sexWithString:SlimModel.sex] == Male) {
...
}
总结:虽然胖瘦Model都一定程度上尝试解决Controller过于臃肿的问题,但是仅仅停留在数据处理、格式化层面,真正的业务逻辑、交互逻辑依然存放于Controller中
MVP
本质上与MVVM没有区别,只是将Controller中关于Model和数据处理的代码移植到Presenter中,但是MVP并不是一个完全剥离业务逻辑的设计模式
架构模型
View
负责界面结构和布局管理,通过Presenter暴露的数据更新和数据获取的接口进行展示
Presenter
负责处理数据,并提供刷新反馈接口,并管理Model。
Model
和MVC中的一样,提供数据模型和映射
MVVM
MVVM起源于前端,这种抽离模型响应式架构在Web开发及小程序开发中已经十分普及,在大厂甚至成为一种标准存在。MVVM的出现,为移动端开发Controller/Activity的瘦身带来启发,刚才讲到的胖Model只从Controller中移植了一些简单的弱业务,而大量的交互逻辑仍存放在Controller中
MVVM力求解决的开发痛点
- 过长的数据响应链,每次数据更新都要向上层传递
- 控制器层代码臃肿。由于UIKit框架的设计原因,UIViewController承担了过多不应承担的责任,Controller内包含了数据、界面、逻辑元素,只能通过规范约束,难以管理
- 架构层面结构划分不清晰,增加了移动端的学习难度
架构模型
Model
和MVC中的瘦Model一样。只承载最基本的数据单元和数据映射。
1
2
3
4
5
6@interface UserModel: NSObject
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, copy) NSString *sex;
@endView
View层包含了UIKit框架下,UIView和UIViewController及其子类。在View层中只需要编写页面展示模板和数据绑定,不写任何业务逻辑。
1
2
3
4
5- (void)awakeFromNib {
[super awakeFromNib];
RAC(self.userNameLabel, text) = RACObserve(self, viewModel.userName);
RAC(self.userSexLabel, text) = RACObserve(self, viewModel.sex);
}ViewModel
ViewModel里涵盖了所有View层绑定需要的数据模型、逻辑函数、事件响应函数。当数据发生改变,View层会响应式渲染更新界面。
RAC对于MVVM的意义
RAC(ReactiveCocoa)并不是MVVM思想的核心,不用RAC也能使用MVVM。但是使用RAC能更好的体现MVVM的精髓,即数据绑定处理、响应式渲染。就像Web开发中,使用React、Vue那样。在MVC中,M和V若无持有关系,在数据传递和渲染时会造成很多麻烦,那么如何把原本松散的两者通过中间层联系起来(数据绑定),在iOS本身并没有太靠谱的办法,在Vue中有这种优雅的写法。
虽然KVO、Notification、block、delegate和target-action都可以用来做数据通信进而实现绑定,但都不如RAC来的《《优雅》》。
关于架构设计的一些观点
控制好Controller的代码量
随着项目的进行、代码量最多只能优化、膨胀不可避免。而在没办法继续精简的前提下、想控制Controller的代码量。就要在可读性和通用性之间进行取舍。该挪走的时候就挪走吧、毕竟梳理一个单独的模块、比梳理一个几千行的Controller要方便多了。
对于MVX如何选择
- 其实完全要看业务性质以及项目复杂度
- 如果你一个页面只有一个UITableView,搞出一些奇淫技巧其实意义不大,徒增烦恼。踏踏实实用MVC对大家都好。
- 如果业务中View存在多种样式风格,底层数据处理存在大量复用的情况,可以使用MVP
- 如果业务相当的复杂,耦合让人浑身难受。做好模块化或者干脆VIPER才是出路。
- 如果你追求如前端MVVM框架(Vue、React)一般的开发体验,可以尝试使用RAC,但不建议单独脱离RAC使用MVVM架构
无论用哪种模式、都要深刻的理解每个模块不同的职责
ps: 本文基于CocoaChina博主kirito_song的文章,并整理了近期项目实践撰写。