前言
谈论他们之前,先了解一点,其他的模式基本上都是从mvc架构上根据不同的业务演化而来,实际上也可以理解为他们为mvc的子集或者或者mvc的变种,他们之间在某种程度上还是可以互通,甚至可以相互借鉴延伸出更适合自己项目的架构
另外项目在不同时期,会有其更适合的架构,若盲目尝试新架构,只会增加工作量,毕竟鱼与熊掌不可兼得,适合的才是最好的,没有最完美的架构,只有最匹配的架构(项目业务、团队规模等,都会有非常大的影响),了解了这点就有利于后面的理解了
好的架构思想在于大家的思考与碰撞,其才能擦出明亮的火花,毕竟一千个人有一千个哈姆雷特
本文会带着ios的 案例demo 进行入门讲解,方便大家理解
MVC
MVC是开发中最常见的架构之一,也是最基本的架构,很多架构都是基于MVC衍生出来的,甚至在一些语言的开发中MVC被称为设计模式中的一种
一般来说,MVC中的交互结构如下所示:
这也是比较标准的交互方式,实际上由于系统上Controller和View的默认嵌套,Controller和View很可能是嵌在一起的,所以也衍生出了很多标准的交互方式
实际工作中,仅仅使用MVC会导致Controller文件较大,因此很多人想出了很多办法来处理
例如:面向对象将View和Model最大化封装,controller中主要负责API的调用和交互处理;在View作为半个控制器,将model嵌入在里面,尽可能处理一些Model逻辑和事件,让View或者Model成为控制器的子分支,还有其他等等
这些在MVC的使用中都没有什么问题,属于正常化,没有什么对于错,唯一的缺点就是View、Model、Controller的关系出现了微妙的变化,他们之间增加了更多的耦合度,虽然Controller里面的代码减少了,但是他们之间的逻辑更加复杂了,如果不是熟悉此开发习惯或者逻辑的人,代码读取难度会直线上升,也就是出现了典型的风格适应问题,且可能是很大的风格变化
因此,如果想避免这些多变的风格,不妨使用标准的MVC,职责划分好,尝试合适的面向对象开发尽可能的对View和Model进行封装,在Controler中仅仅负责API的调用和交互的处理,合理利用分类继承等手段来减少Controller中的代码
案例演示
案例使用的是一个标准的TableView视图,里面包括的数据获取DataSource、View、Model、Controller的处理,其中DataSource负责数据的获取,例如:请求网络数据,获取本地数据
下面展示一下普通的MVC的案例代码演示:
- (void)viewDidLoad {
[super viewDidLoad];
[self setupTableView];
[self getDataSource];
}
- (void)setupTableView {
self.tableView = [[UITableView alloc] initWithFrame:self.view.frame
style:UITableViewStylePlain];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.tableView registerNib:[UINib nibWithNibName:@"MVCCell" bundle:nil]
forCellReuseIdentifier:reuseIdentifier];
[self.view addSubview:self.tableView];
}
- (void)getDataSource {
[MVCDataSource getMVCData:^(NSArray<MVCModel *> * _Nonnull dataList) {
self.dataList = dataList;
[self.tableView reloadData];
}];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_dataList count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MVCCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier
forIndexPath:indexPath];
//也有将model直接嵌入view中,但是不推荐,UI更改应当相对独立一些
//控制器做控制器要做的事情,模型做模型的事情
MVCModel *model = _dataList[indexPath.row];
cell.titleLabel.text = model.name;
cell.contentLabel.text = model.content;
__weak typeof(self) wself = self;
[cell setOnTapBlock:^(MVCCell * _Nonnull cell) {
[wself onChanged];
}];
return cell;
}
复制代码
上面代码逻辑相信很容易看明白
DataSource:负责获取数据的封装,在控制器中能一步直接获取到要使用的数据
View:主要是对子视图的封装,并对外暴露赋值方式,不直接和Model进行交互
Model:主要负责数据模型的定义,或者是一些数据的处理操作,又或者是一些业务操作,又或者是通用操作
Controller:主要负责View、Model、DataSource等API的调用,即整合资源,还有就是View和Model之间的交互逻辑
MVP
相比较ios,MVP更早出现在Android之间,据说也是其中的无奈,后来ios中也流行起来,且效果更加明显,其也是基于MVC架构的一个演变,将Controller中的View和Model的逻辑放到了Present中来处理,其使用效果非常优秀,不少开发者都在使用此架构
交互逻辑如下图所示:
这里面Present持有View和Model,主要负责View和Model之间的整合逻辑,也就是扮演者原来的控制器的角色,而控制器Controller则负责present的设置等(即Controller和Present之间的交互),还有数据的获取(也可以在Present中获取), 控制器之间的业务交互等
可以看到present的出现,将控制器大部分代码都可以迁移到present当中去,一些需要在控制器中处理的逻辑(例如:控制器之间的交互),则需要在控制器中处理,这样大部分业务都跑到Present当中去了,也就是说业务相关逻辑处理,大部分都在present当中修改即可
注意:除此之外,还有一种面向业务的MVP模式,与上面不同的是,即不涉及到业务的UI等逻辑继续放到控制器controller当中,只有涉及到业务相关的View和Model等相关处理才会放到Present当中,这种情况在初期开发,对于后台和UI等变化相对巨大的情况,非常适用,也就是面向业务开发的MVP模式
而这两种方式,看了更后面的模式之后,相信加入自己的理解,会有更加合理的利用方式
案例演示
下面用案例演示第二种MVP,即面向业务的MVP模式代码案例演示:
控制器相关代码:
- (void)viewDidLoad {
[super viewDidLoad];
[self setupPresent];
[self setupTableView];
}
//实际使用中,根据情况tableView也可以独立到view层中,如果代码不多,那么没必要
- (void)setupTableView {
self.tableView = [[UITableView alloc] initWithFrame:self.view.frame
style:UITableViewStylePlain];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.tableView registerNib:[UINib nibWithNibName:@"MVCCell" bundle:nil]
forCellReuseIdentifier:reuseIdentifier];
[self.view addSubview:self.tableView];
}
- (void)setupPresent {
self.present = [[MVPPresent alloc] init];
self.present.tableView = self.tableView;
[self.present getDataSource];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.present && self.present.dataList ? self.present.dataList.count : 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MVPCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier
forIndexPath:indexPath];
//直接将赋值相关操作全部交给present,这些涉及到业务相关的内容
self.present.cellUpdateBlock(tableView, cell, indexPath);
return cell;
}
复制代码
present相关代码
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong, readonly) NSArray *dataList;
@property (nonatomic, strong) void(^cellUpdateBlock)(UITableView *tableView, MVPCell*cell,
NSIndexPath *indexPath);
- (instancetype)init
{
self = [super init];
if (self) {
[self getDataSource];
}
return self;
}
- (void)getDataSource {
[MVPDataSource getMVPData:^(NSArray<MVCModel *> * _Nonnull dataList) {
//除了加载数据,数据内容的处理也可以在这里面,例如内容计算、筛选,
//当然根据业务和复杂度,可以再进一步封装划分
self.dataList = dataList;
[self.tableView reloadData];
}];
}
- (void (^)(UITableView *, MVPCell *, NSIndexPath *))cellUpdateBlock {
__weak typeof(self) wself = self;
_cellUpdateBlock = ^(UITableView *tableView, MVPCell *cell, NSIndexPath *indexPath) {
__strong typeof(self) sself = wself;
MVPModel *model = sself.dataList[indexPath.row];
cell.titleLabel.text = model.name;
cell.contentLabel.text = model.content;
// cell.clickDelegate = sself;
//这里也可以实现点击的代理等相关操作业务相关问题,毕竟视图这里都可以访问的到
[cell setOnTapBlock:^(MVCCell * _Nonnull cell) {
[wself onChangeItem:model cell:cell indexPath:indexPath];
}];
};
return _cellUpdateBlock;
}
- (void)onChangeItem:(MVPModel *)model cell:(MVCCell *)cell
indexPath:(NSIndexPath *)indexPath {
model.content = [NSString stringWithFormat:@"我是更改过的内容:%ld", indexPath.row];
//如果想要model改动的同时,自动更新View,则需要对view添加对model的监听,这种时候,mvp代码又会变得复杂
cell.contentLabel.text = model.content;
[self.tableView reloadData];
}
复制代码
通过上面可以看到,一部分不变的View逻辑直接放到了controller当中,而涉及到网络数据和相关View部分的逻辑均交给Present代为处理,此操作将基本视图、业务逻辑视图、业务逻辑完全分离,当视图变动,只需要更改视图,或者根据情况present中更改赋值方式即可,或者网路请求数据格式调整,更改完网络请求数据,根据情况直接到present中调整即可,目的明确,使用灵活,非常适合前期开发,甚至调整之后,可以直接一直用于整个项目
MVVM
MVVM也是在某次开发大会中被发掘出来,其使用方式之新奇,更是令不少人神往,其也是MVC衍生出来的一种架构模式,也非常优秀,受到不少人的大爱,使用过程推荐使用RAC相关架构(由于RAC架构的侵入性较强,且对新人需要一定门槛,个人不是非常推荐),实际使用中其实没必要使用RAC相关架构也可以正常使用
后面也介绍其使用过程中的优缺点,以及与一些架构的相似之处,相信理解后,MVVM可能真的是被神话的架构,并非吊打一切,只是一个阶段的产物罢了
其交互逻辑图如下所示
看上图,可以看到ViewModel主要负责View和Model的整合和绑定,当Model发生改变时,View也发生改变,当View响应时,Model也可以随之发生改变,而一个Controller也是可以由一个或者多个ViewModel组成,每一个ViewModel给控制器提供一套相对完善的View处理,这也是ViewModel设计的初衷
注意:看到了上面的结构,你也许会想到,这个结构怎么和MVP这么像?没错,MVP的Present经过分离调整,就是另外一个MVVM,很多人很早已经将MVP的Present分发出来了,但是却不叫MVVM,甚至瞧不起MVVM,也因此有说MVVM是MVP的另外一个变种,如下图所示
而我看感觉,MVP和MVVM是完全不会冲突的一个架构,MVP将Controler的部分逻辑分发出来,ViewModel开发出了数据和视图绑定的概念,配合观察者模式,更适合应用于模型和数据固定的交互关系,在这种固定模式下使用ViewModel可以让人更好的理解其中的关系,其更像一个整体,而Present更像是一个整合器
因此在合适的使用使用合适的结构,方为上上之策,你觉得是灵活使用好,还是统一架构好呢?
代码案例
代码案例使用起来和MVP很像,这里就介绍Controller和ViewModel,可以看一下里面的逻辑,其中观察者模式中使用了KVOController来配合,而不是使用庞大的RAC(ReativeCocoa)框架,如下所示
Controller代码
- (void)viewDidLoad {
[super viewDidLoad];
self.viewModelList = [NSMutableArray array];
[self setupTableView];
[self getDataSource];
}
- (void)setupTableView {
self.tableView = [[UITableView alloc] initWithFrame:self.view.frame
style:UITableViewStylePlain];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.tableView registerNib:[UINib nibWithNibName:@"MVCCell" bundle:nil]
forCellReuseIdentifier:reuseIdentifier];
[self.view addSubview:self.tableView];
}
- (void)getDataSource {
[MVVMDataSource getMVVMData:^(NSArray<MVVMModel *> * _Nonnull dataList) {
[dataList enumerateObjectsUsingBlock:^(MVVMModel * _Nonnull obj,
NSUInteger idx, BOOL * _Nonnull stop) {
MVVMViewModel *viewModel = [MVVMViewModel new];
viewModel.coreModel = obj;
[self.viewModelList addObject:viewModel];
}];
[self.tableView reloadData];
}];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.viewModelList count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MVVMCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier
forIndexPath:indexPath];
//实际上一般是一个view对应一个viewModel,复用里面一般用的少,这里只是演示使用
MVVMViewModel *viewModel = self.viewModelList[indexPath.row];
viewModel.coreView = cell;
viewModel.delegate = self;
[viewModel update];
return cell;
}
//长按跳转,这个是viewModel和controller之间的交互
- (void)navigateMVVMController {
LSMVVMController *testNewVC = [LSMVVMController new];
[self presentViewController:testNewVC animated:YES completion:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[testNewVC dismissViewControllerAnimated:YES completion:nil];
});
}
复制代码
ViewModel代码
- (instancetype)init
{
self = [super init];
if (self) {
_kvoController = [FBKVOController controllerWithObserver:self];
}
return self;
}
- (void)update {
//此模型里面的内容,可能会跨越多个界面,因此使用view与与其绑定
//可以使得使用同一个模型的view在model更改后同时响应更改
[_kvoController observe:self.coreModel keyPath:@"name"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
block:^(id _Nullable observer, MVVMModel *_Nonnull object,
NSDictionary<NSString *,id> * _Nonnull change) {
self.coreView.titleLabel.text = object.name; // change[@"new"];
}];
//注意KVOControlelr并不会与self造成循环引用,注意block的持有者并不是self,而是_FBKVOInfo
[_kvoController observe:self.coreModel keyPath:@"content"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
block:^(id _Nullable observer, MVVMModel *_Nonnull object,
NSDictionary<NSString *,id> * _Nonnull change) {
self.coreView.contentLabel.text = object.content; // change[@"new"];;
}];
__weak typeof(self) wself = self;
[self.coreView setOnTapBlock:^(MVCCell * _Nonnull cell) {
[wself onClickToUpdate];
}];
[self.coreView setOnLongPressBlock:^(MVCCell * _Nonnull cell) {
if ([wself.delegate respondsToSelector:@selector(navigationController)] ) {
[wself.delegate navigateMVVMController];
}
}];
}
//更改不一定是在当前界面
- (void)onClickToUpdate {
self.coreModel.name = @"更改的标题";
self.coreModel.content = @"更改过的内容";
}
复制代码
通过上面代码,可以看到MVVM的优势,非常适用于一个模型作用域多个UI界面的场景,当模型改变,其他涉及到的UI都会发生改变,这也是观察者模式的优势,其更加合理的利用此优势,来完成响应的功能
在我看来他更像是一个设计模式应用合理运用的一个开始
中间路由router
中间路由router也是由MVC衍生出来的一个非常优秀的架构,他算是一个面向协议编程的一个典型架构,其思想与其他架构完全不冲突,可以与MVP、MVVM等结合使用,其交互逻辑图如下所示
上图我故意没有在Context多个端全部放置Present,而是夹杂着一个ViewModel,表示着其结构有着无线的可能性,并不是局限于一种模式,实际工作中一般使用同一种架构
这种架构中可以看到,context作为交互的中间路由,其可以与Controller、所有Present、所有ViewModel进行交互,因此Present与Present之间的交互也可以通过Context进行桥接,可以通过创建协议,使用面向协议方式进行交互,这样可以使得交互更加清晰
通过上面的可以看到他们之间很多都是双向箭头,那么持有关系是怎样的呢,哪里应该是weak,哪里必须strong
MVC中都是Controler直接持有View和Model
MVP中都是Controller直接持有Present,Present持有View和Model
MVVM中都是Controller直接持有ViewModel,ViewModel持有View和Model
而中间路由router明显复杂一些,为了避免Context不是创建完毕就马上释放,Controller强持有Context,Context引用Controller以保持与Controller的联系,且避免引用循环;而Context与Present、ViewModel之间的关系也是类似,为了保证他们都随着Controller的释放而释放,且不会创建完毕就立即释放,Context应当强持有所有Present和ViewModel,而Present、ViewModel则弱引用Context以保证联系
通过上面逻辑,就得以保证他们之间没有循环引用关系,Controller里面的组件会随着Controller的释放而集体释放,详细可以参考Router的代码
代码案例
代码的实现模块跟上面介绍的略有不同,故意多添加了一个模块,如果前面的你都看了,相信这种结构也会很容易看明白,其结构如下图所示,相信你的实际项目也许会用得到
context实现
@class LSRouteHeaderModel, LSRouterRowModel;
@protocol LSRouteHeadViewDelegate <NSObject>
- (void)onUpdateWithHeadModel:(LSRouteHeaderModel *)headModel;
@end
@protocol LSRouteTableViewDelegate <NSObject>
- (void)onUpdateWithRowModel:(LSRouterRowModel *)rowModel;
@end
@class LSRouterController, LSRouterPresent, LSRouteHeadPresent;
@interface LSRouterContext : NSObject
@property (nonatomic, weak) LSRouterController *routerController;
@property (nonatomic, strong) LSRouterPresent *present;
@property (nonatomic, strong) LSRouteHeadPresent *headPresent;
@end
复制代码
Controller实现
@property (nonatomic, strong) LSRouterContext *context;
- (void)setup {
self.context = [[LSRouterContext alloc] init];
self.context.routerController = self;
//初始化头部视图相关
self.context.headPresent = [[LSRouteHeadPresent alloc] init];
self.context.headPresent.context = self.context;
UIView *headView = [self.context.headPresent loadHeadViewWithFrame:
CGRectMake(0, 0, self.view.frame.size.width, 50)];
[self.view addSubview:headView];
//加载数据
[self.context.headPresent loadDataSouce];
//初始化表格视图
self.context.present = [[LSRouterPresent alloc] init];
self.context.present.context = self.context;
UIView *homeView = [self.context.present loadTableView];
homeView.frame =
CGRectMake(0, 50, self.view.frame.size.width, self.view.frame.size.height - 50);
[self.view addSubview:homeView];
//加载数据
[self.context.present loadDataSource];
//设置代理关系,有利于交互,这一部分代理关系的设置
self.context.present.delegate = self.context.headPresent;
self.context.headPresent.delegate = self.context.present;
}
复制代码
TableView的Present实现
@interface LSRouterPresent : NSObject<LSRouteHeadViewDelegate>
@property (nonatomic, weak) id<LSRouteTableViewDelegate> delegate;
@property (nonatomic, weak) LSRouterContext *context;
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *rowList;
- (UITableView *)loadTableView;
- (void)loadDataSource;
@end
@implementation LSRouterPresent
- (UITableView *)loadTableView {
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero
style:UITableViewStylePlain];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.tableView registerNib:[UINib nibWithNibName:@"MVCCell" bundle:nil]
forCellReuseIdentifier:cellIdentifier];
return self.tableView;
}
- (void)loadDataSource {
//这里为了方便展示结构,row使用的是同一串数据
[LSRouterDataSource getRouterData:^(NSArray<LSRouterRowModel *> * _Nonnull rowList) {
NSMutableArray *dataList = [NSMutableArray array];
[rowList enumerateObjectsUsingBlock:^(LSRouterRowModel * _Nonnull obj,
NSUInteger idx, BOOL * _Nonnull stop) {
LSRouteCellViewModel *viewModel = [LSRouteCellViewModel new];
viewModel.coreModel = obj;
[dataList addObject:viewModel];
}];
self.rowList = dataList;
[self.tableView reloadData];
}];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.rowList ? self.rowList.count : 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
LSRouterCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier
forIndexPath:indexPath];
//实际上一般是一个view对应一个viewModel,复用里面一般用的少,这里只是演示使用
LSRouteCellViewModel *viewModel = self.rowList[indexPath.row];
viewModel.coreView = cell;
viewModel.delegate = self;
[viewModel update];
return cell;
}
- (void)onTapUpdate:(LSRouterRowModel *)rowModel {
if ([self.delegate respondsToSelector:@selector(onUpdateWithRowModel:)]) {
[self.delegate onUpdateWithRowModel:rowModel];
}
}
//更新视图
- (void)onUpdateWithHeadModel:(LSRouteHeaderModel *)headModel {
[self.rowList enumerateObjectsUsingBlock:^(LSRouteCellViewModel * _Nonnull obj,
NSUInteger idx, BOOL * _Nonnull stop) {
obj.coreModel.content = headModel.content;
}];
}
复制代码
顶部视图HeadView的present实现
@interface LSRouteHeadPresent : NSObject<LSRouteTableViewDelegate>
@property (nonatomic, weak) id<LSRouteHeadViewDelegate> delegate;
@property (nonatomic, weak) LSRouterContext *context;
@property (nonatomic, strong) LSRouteHeadView *coreView;
@property (nonatomic, strong) LSRouteHeaderModel *coreModel;
- (UIView *)loadHeadViewWithFrame:(CGRect)frame;
- (void)loadDataSouce;
@end
@implementation LSRouteHeadPresent
- (UIView *)loadHeadViewWithFrame:(CGRect)frame {
self.coreView = [[LSRouteHeadView alloc] initWithFrame:frame];
__weak typeof(self) wself = self;
[self.coreView setOnUpdateBlock:^(LSRouteHeadView * _Nonnull coreView) {
[wself onClickToUpdateAllCell];
}];
return self.coreView;
}
- (void)loadDataSouce {
self.coreModel = [[LSRouteHeaderModel alloc] init];
self.coreModel.name = @"我是测试的标题内容";
self.coreModel.content = @"我是测试的副标题内容,这部分可以更新或者使用";
self.coreView.lblHead.text = self.coreModel.name;
}
- (void)onClickToUpdateAllCell {
if ([self.delegate respondsToSelector:@selector(onUpdateWithHeadModel:)]) {
[self.delegate onUpdateWithHeadModel:self.coreModel];
}
}
- (void)onUpdateWithRowModel:(LSRouterRowModel *)rowModel {
self.coreView.lblHead.text = rowModel.name;
NSLog(@"LSRouterRowModel-content:%@", rowModel.content);
}
复制代码
上面仅仅是部分中间路由router的部分代码,详情可以查看代码,相信你结合图可以看的更清晰,并且会有自己的理解
最后
看了上面的中间路由Router有什么感想,如果多个控制器中间还有交互呢,可以试着将Controller当做Present再试试看,会清晰很多,从局部看总体,再从总体看局部,相信会是另一种感悟
没有最好的架构,只有更好的架构,一百个人有一百个哈姆雷特,可是让每一个哈姆雷特都能理解其他哈姆雷特,这就是能力,需要我们更多的积累知识
最后,本文不是终结,而是开始,大家一起努力,让架构们碰撞出爱情的火花吧!