iOS八股文(十八)架构模式

664 阅读6分钟

MVC

相信提到MVC(Model View Controller),iOSer再熟悉不过了,苹果爸爸在官方也推荐使用这种架构模式,MVC各层的分工相信都倒背如流了,这里也不再赘述。

image.png

MVC的缺点:

  • Controller过重,controller需要做的事情太多,例如view的代理,数据的请求,业务逻辑,视图的约等等代码都有可能在Controller中。
  • 容易耦合,view对model强引用,在view种避免不了对modle的操作,controller中也引用view,controller中也会有布局约束代码。

MVC的优点:

  • 简单,容易上手,比较容易理解,项目有新同学加入,可快速开发。
  • Controller统筹全局,这一点既是优点,也是缺点。
  • 代码量少,因为相比于MVVM、MVP不需要额外新建ViewModel类、Presenter类,文件、类的数量肯定是要少的。

拯救ViewContrller

相信这个问题是iOS程序猿的一个噩梦,谁还没见过上千行代码的Controller呢?大部分的工程里面都有几个又臭又长的Controller。而这个繁重的Contoller里面内容肯定逃不过一下几部分:

  1. 复杂的视图布局和约束
  2. 庞大的数据请求和模型转化
  3. 啰嗦的业务代码
  4. 繁种的代理方法
  5. 茫茫多的懒加载或者初始化
  6. ......等等等等 这些代码让我们的Controller承受了他不该承受的压力。接下让就对症下药,解决这些问题。

MVVM

某个伟人曾经说过,计算机中的大多数问题都可以使用一个中间层来解决。这时候MVVM(Model View ViewModel)就应运而生。

image.png

这个图是在网上搜罗的,感觉对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

cell对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架构中会引入一个概念- 面向协议编程。

image.png

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

image.png 这种架构模式是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,反而多增加几个类,在项目优化的时候,我们又在绞尽脑汁得减少类的使用,索性在这时候就放弃项目整体的架构,转而使用更简单的架构也是一种不错的选择。如果公司项目强制要求,还是遵从项目代码规范。

参考链接