MVC
相信提到MVC(Model View Controller)
,iOSer再熟悉不过了,苹果爸爸在官方也推荐使用这种架构模式,MVC各层的分工相信都倒背如流了,这里也不再赘述。
MVC的缺点:
Controller过重
,controller需要做的事情太多,例如view的代理,数据的请求,业务逻辑,视图的约等等代码都有可能在Controller中。容易耦合
,view对model强引用,在view种避免不了对modle的操作,controller中也引用view,controller中也会有布局约束代码。
MVC的优点:
简单
,容易上手,比较容易理解,项目有新同学加入,可快速开发。Controller统筹全局
,这一点既是优点,也是缺点。代码量少
,因为相比于MVVM、MVP不需要额外新建ViewModel类、Presenter类,文件、类的数量肯定是要少的。
拯救ViewContrller
相信这个问题是iOS程序猿的一个噩梦,谁还没见过上千行代码的Controller呢?大部分的工程里面都有几个又臭又长的Controller。而这个繁重的Contoller里面内容肯定逃不过一下几部分:
- 复杂的视图布局和约束
- 庞大的数据请求和模型转化
- 啰嗦的业务代码
- 繁种的代理方法
- 茫茫多的懒加载或者初始化
- ......等等等等 这些代码让我们的Controller承受了他不该承受的压力。接下让就对症下药,解决这些问题。
MVVM
某个伟人曾经说过,计算机中的大多数问题都可以使用一个中间层来解决。这时候MVVM(Model View ViewModel)
就应运而生。
这个图是在网上搜罗的,感觉对MVVM
诠释得还是比较准确。首先,ViewController划分到了View层
。而在View层和Model层
有个中间层ViewModel层
。View层的指责布局视图,约束视图,ViewModel层指责业务逻辑,网路请求,Model层数据的定义层。通过MVVM架构,View不再直接引用用Model,这样就不会有在View层处理Model的代码,同时ViewControler中的网络请求,业务代码也来到了ViewModel层。貌似看已经解决了MVC的量大缺陷。
Talk is cheap,show you code
。
以这个需求为例:点击关注/取消关注的时候,首先按钮状态立马置反,然后进行网络请求接口(关注/取消关注接口),如果接口成功toast提示,如果接口请求失败,还原按钮状态。
View的.h中的代码
@interface OSArchitectureCell : UITableViewCell
@property (nonatomic, strong) OSAuthorViewModel *viewModel;
@end
cel
l对viewMode
强引用。
在看.m中的关键代码
//view 绑定视图
- (void)setViewModel:(OSAuthorViewModel *)viewModel {
_viewModel = viewModel;
//头像
self.authorIcon.image = [UIImage imageNamed:viewModel.avater];
//名字
self.authorNameLabel.text = viewModel.name;
// 是否关注
[self updateFollowBtn:viewModel.isFollow];
__weak typeof(self) weakSelf = self;
//关注回调
[viewModel setRefreshFollowState:^(BOOL isFollow) {
[weakSelf updateFollowBtn:isFollow];
}];
}
//按钮样式改变
- (void)updateFollowBtn:(BOOL)isFollow {
if (isFollow) {
_followBtn.backgroundColor = [UIColor grayColor];
[_followBtn setImage:nil forState:UIControlStateNormal];
[_followBtn setTitle:@"取消关注" forState:UIControlStateNormal];
[_followBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
} else {
_followBtn.backgroundColor = [UIColor yellowColor];
[_followBtn setImage:[UIImage imageNamed:@"icon_follow"] forState:UIControlStateNormal];
[_followBtn setTitle:@"关注" forState:UIControlStateNormal];
[_followBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
}
}
//按钮点击响应
- (void)followAction:(UIButton *)sender {
// mvvm
//视图更新
[self updateFollowBtn:!self.viewModel.isFollow];
//数据改变,网络请求
[self.viewModel requestFollow];
}
再看ViewModel中的.h文件。
@interface OSAuthorViewModel : NSObject
@property (nonatomic, copy) NSString *avater;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) BOOL isFollow;
- (void)requestFollow;
@property (nonatomic, copy) void(^refreshFollowState)(BOOL isFollow);
@end
.m中的关键代码
- (void)requestFollow {
// 改变数据
self.isFollow = !self.isFollow;
//网络请求
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
// 模拟失败
if (arc4random_uniform(20) > 15) {
if (self.refreshFollowState) {
self.refreshFollowState(!self.isFollow);
}
//模拟toast
NSLog(@"%@失败",self.isFollow ? @"关注": @"取消关注");
self.isFollow = !self.isFollow;
} else {
if (self.refreshFollowState) {
self.refreshFollowState(self.isFollow);
}
NSLog(@"%@成功",self.isFollow ? @"关注": @"取消关注");
}
});
});
}
这里我的ViewModel没有引用Model,其实是需要引用Model的。改变数据的时候需要改变Model的数据。
再看Controller中cell重用的这块逻辑:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
OSArchitectureCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([OSArchitectureCell class]) forIndexPath:indexPath];
OSAuthorViewModel *viewModel = self.vm.authorViewModels[indexPath.row];
//绑定ViewModel
cell.viewModel = viewModel;
return cell;
}
这就是这块需求整体的MVVM架构的主要代码。这里ViewModel跟View的通信使用的是Block的方式。也就是说,如果视图引起的model改变,因为View对ViewModel 有引用,ViewModel对Model有引用,所以可以直接通过 [self.viewModel xxx]
这种方式调用。而Model的改变要通信到View层需要通过ViewModel的Block(refreshFollowState
)调用达到目的。而viewModel的block是在bindViewModel
的时候赋值的,在赋值的时候一定要注意循环引用的问题。
而view跟viewModel的通信
有很多的方式,这里的Block
这种是轻量级、简单化的。也有使用RAC(ReactitveCocoa),Rxswfit这种响应式编程的。但其中重点的还是MVVM的这种结构的思想,对于通信,活着绑定方式可以自行选择。
MVP
其实个人感觉MVP(Model View Presenter)
这种架构模式和MVVM非常相似,都是通过中间层(Persenter、ViewModel)来解决Controller代码繁重的问题的。在MVP中将View和ViewController都视作View层,用来处理UI展示逻辑,交互响应,其中Controller的主要任务是建立关系,Presenter层用来处理业务逻辑。
在MVP架构中会引入一个概念- 面向协议编程。
demo示例
同样还是上面的需求,看一看MVP的实现
View层 Controller中
- (void)viewDidLoad {
[super viewDidLoad];
self.view = self.contentView;
self.presenter = [[OSAuthorPresenter alloc] init];
self.presenter.delegate = self;
}
- (void)reloadView {
[self.contentView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
OSAuthorMvpCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([OSAuthorMvpCell class]) forIndexPath:indexPath];
OSAuthorModel *model = self.presenter.dataList[indexPath.row];
cell.authorIcon.image = [UIImage imageNamed:model.avater];
cell.authorNameLabel.text = model.name;
cell.indexPath = indexPath;
[cell updateFollowBtn:model.isFollow];
return cell;
}
cell中:
- (void)followAction:(UIButton *)sender {
if ([self.delegate respondsToSelector:@selector(didClickFollow:indexpath:)]) {
[self.delegate didClickFollow:self.isFollow indexpath:self.indexPath];
}
}
preshent中定义协议:
@protocol OSAuthorPresenter <NSObject>
@optional
- (void)didClickFollow:(BOOL *)isFollow indexpath:(NSIndexPath *)indexpath;
- (void)reloadView;
@end
@implementation OSAuthorPresenter
- (void)loadData {
// 模拟网络请求
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
OSAuthorModel *author1 = [[OSAuthorModel alloc] init];
author1.avater = @"avatar-1";
author1.name = @"小红";
author1.isFollow= YES;
OSAuthorModel *author2 = [[OSAuthorModel alloc] init];
author2.avater = @"avatar-2";
author2.name = @"小蓝";
author2.isFollow= NO;
OSAuthorModel *author3 = [[OSAuthorModel alloc] init];
author3.avater = @"avatar-3";
author3.name = @"小绿";
author3.isFollow= YES;
self.dataList = @[author1, author2, author3];
if ([self.delegate respondsToSelector:@selector(reloadView)]) {
[self.delegate reloadView];
}
});
});
}
- (void)didClickFollow:(BOOL *)isFollow indexpath:(NSIndexPath *)indexpath {
//..code..
}
@end
Clean Architecture
这种架构模式是Bob大叔在12年提出的,这套理论适用于所有的程序。大致是讲应用程序分为4个层,但又不准确是4个层,也有可能更多。其中有个规则(
The Dependency Rule
)原文中最重要的一句话就是:
Nothing in an inner circle can know anything at all about something in an outer circle.
里面的圈子根本不知道外圈干了什么事。即里圈不依赖于外圈,回过头再看一看MVP的代码中Present中使用代理这种方式作为一个接口,抽象化View的动作,也是做到里里圈的Preset 不知道外圈的UI(View层)做了什么,没有产生依赖。
架构的意义
架构的意义在于让项目结构清晰,更容易维护,迭代起来更加轻松。架构模式之间没有三六九等,只有项目适合于某种架构模式,并不存在某种架构模式比另一种架构模式更加优秀,像是平时写的一个静态页面,如果采用MVC写,一个文件一个类就可以完全搞定,如果非要使用MVVM,MVP,反而多增加几个类,在项目优化的时候,我们又在绞尽脑汁得减少类的使用,索性在这时候就放弃项目整体的架构,转而使用更简单的架构也是一种不错的选择。如果公司项目强制要求,还是遵从项目代码规范。