iOS之MVP架构模式

语言: CN / TW / HK

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情

MVP面向协议编程,即Model View Presenter(模型 视图 协调器)。

特征

  • 任务均摊:将最主要的任务划分到PresenterModel,而View的功能较少
  • 可测试性:由于一个功能简单的View层,所以测试数业务逻辑变得简单
  • 易用性:比使用MVC模式的代码量更多,但MVP模式具备非常清晰的结构 所以,MVCMVP的区别就是,在MVPModelView没有直接通信。

设计模式

  • Model层不仅仅是创建一个数据对象,还应该包含网络请求,以及数据Cache的操作
  • View层就是一些封装、重用
  • Presenter层并不涉及数据的网络请求和Cache操作,只是搭建Model层和View层的一个桥梁

优势

  • 模型与视图完全分离,修改视图而不影响模型
  • 可以更高效地使用模型,因为所有交互都在Presenter
  • 把逻辑放在Presenter中,可以脱离用户接口进行单元测试
  • 可以将一个Presener用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁

UIModel解耦

创建LGProtocol,实现Cell中按钮点击的事件接口

```

import

@protocol LGProtocol

  • (void)didClickNum:(NSString )num indexpath:(NSIndexPath )indexpath;

@end `` 在Present中,实现LGProtocol协议的didClickNum`方法

```

import

import "Model.h"

import "LGProtocol.h"

import

@interface Present : NSObject

@property (nonatomic, strong) NSMutableArray *dataArray; @property (nonatomic, weak) id delegate;

@end

@implementation Present

  • (instancetype)init{ if (self = [super init]) { [self loadData]; } return self; }

  • (void)loadData{

    NSArray *temArray = @[ @{@"name":@"HK",@"imageUrl":@"http://HK",@"num":@"99"}, @{@"name":@"KD",@"imageUrl":@"http://KD",@"num":@"99"}, @{@"name":@"CC",@"imageUrl":@"http://CC",@"num":@"99"}, @{@"name":@"KC",@"imageUrl":@"http://KC",@"num":@"59"}, @{@"name":@"LA",@"imageUrl":@"http://LA",@"num":@"24"}];

    for (int i = 0; i<temArray.count; i++) { Model *m = [Model modelWithDictionary:temArray[i]]; [self.dataArray addObject:m]; } }

pragma mark -LGProtocol

  • (void)didClickNum:(NSString )num indexpath:(NSIndexPath )indexpath{

    @synchronized (self) { if (indexpath.row < self.dataArray.count) {

        Model *model = self.dataArray[indexpath.row];
        model.num    = num;
    }
    

    } }

pragma mark - lazy

  • (NSMutableArray *)dataArray{ if (!_dataArray) { _dataArray = [NSMutableArray arrayWithCapacity:10]; } return _dataArray; }

@end `` 在MVCTableViewCell中,删除UIModel的数据绑定。在按钮点击的交互方法中,调用delegatedidClickNum`方法

``` - (void)didClickAddBtn:(UIButton *)sender{ self.num++; }

  • (void)setNum:(int)num{ _num = num; self.numLabel.text = [NSString stringWithFormat:@"%d",self.num];

    // 发出响应 model delegate UI if (self.delegate && [self.delegate respondsToSelector:@selector(didClickNum:indexpath:)]) { [self.delegate didClickNum:self.numLabel.text indexpath:self.indexPath]; } }

// 删除UI对Model的数据绑定 //- (void)setModel:(Model *)model{ // _model = model; // self.numLabel.text = model.num; // self.nameLabel.text = model.name; //} `` 来到VC代码中LMDataSourceBlock,完成CellUIModel的数据绑定,以及delegateindexPath`的设置

```

import "MVCViewController.h"

import "LMDataSource.h"

import "MVCTableViewCell.h"

import "Present.h"

import "Model.h"

import "LGView.h"

@interface MVCViewController ()

@property (nonatomic, strong) LGView lgView; @property (nonatomic, strong) LMDataSource dataSource; @property (nonatomic, strong) Present *pt;

@end

@implementation MVCViewController

  • (void)viewDidLoad { [super viewDidLoad];

    // 建立关系 self.view = self.lgView; [self.dataSource setDataArray:self.pt.dataArray]; [self.lgView setDataSource:self.dataSource]; }

pragma mark - lazy

// UI布局代码 - (LGView *)lgView{

if(!_lgView){
    _lgView = [[LGView alloc] initWithFrame:self.view.bounds];
}

return _lgView;

}

// 数据提供层 - (Present *)pt{

if(!_pt){
    _pt = [[Present alloc] init];
}

return _pt;

}

// 数据代理层 - (LMDataSource *)dataSource{

if(!_dataSource){
    __weak typeof(self) weakSelf = self;
    _dataSource = [[LMDataSource alloc] initWithIdentifier:[MVCTableViewCell reuserId] configureBlock:^(MVCTableViewCell *cell, Model *model, NSIndexPath *indexPath) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        cell.numLabel.text  = model.num;
        cell.nameLabel.text = model.name;
        cell.delegate       = strongSelf.pt;
        cell.indexPath      = indexPath;
    }];
}

return _dataSource;

}

@end ```

双向绑定

开发中遇到以下需求时,需要UI和Model进行双向绑定。例如:触发CelldidClickAddBtn事件,当num++大于5UI层只展示前两条数据

修改LGProtocol,增加UI刷新的事件接口

```

import

@protocol LGProtocol

  • (void)didClickNum:(NSString )num indexpath:(NSIndexPath )indexpath;

  • (void)reloadUI;

@end `` 修改Present中的didClickNum方法,增加num大于5,只保留前两条数据,并调用delegatereloadUI方法刷新UI`

``` - (void)didClickNum:(NSString )num indexpath:(NSIndexPath )indexpath{

@synchronized (self) {
    if (indexpath.row < self.dataArray.count) {

        Model *model = self.dataArray[indexpath.row];
        model.num    = num;

        if ([num intValue] > 5) {

            [self.dataArray removeAllObjects];

            NSArray *temArray =
            @[
              @{@"name":@"HK",@"imageUrl":@"http://HK",@"num":@"99"},
              @{@"name":@"KC",@"imageUrl":@"http://KC",@"num":@"99"}];

            for (int i = 0; i<temArray.count; i++) {
                Model *m = [Model modelWithDictionary:temArray[i]];
                [self.dataArray addObject:m];
            }

            if (self.delegate && [self.delegate respondsToSelector:@selector(reloadUI)]) {
                [self.delegate reloadUI];
            }
        }
    }
}

} `` 修改LGView,实现LGProtocol协议的刷新UI`方法

```

import

import "MVCTableViewCell.h"

import "LGProtocol.h"

@interface LGView : UIView

@property (nonatomic, strong) UITableView *tableView;

  • (void)setDataSource:(id)dataSource;

@end

@implementation LGView

  • (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) {

    [self addSubview:self.tableView];
    

    } return self; }

  • (void)setDataSource:(id)dataSource{ self.tableView.dataSource = dataSource; }

  • (void)reloadUI{ [self.tableView reloadData]; }

  • (UITableView *)tableView{ if (!_tableView) { _tableView = [[UITableView alloc] initWithFrame:self.frame style:UITableViewStylePlain]; _tableView.tableFooterView = [UIView new]; _tableView.backgroundColor = [UIColor whiteColor]; [_tableView registerClass:[MVCTableViewCell class] forCellReuseIdentifier:[MVCTableViewCell reuserId]]; } return _tableView; }

@end `` 修改MVCViewController,将UIModel`建立关系

``` - (void)viewDidLoad { [super viewDidLoad];

// 建立关系
self.view = self.lgView;
[self.dataSource setDataArray:self.pt.dataArray];
[self.lgView setDataSource:self.dataSource];
//将UI和Model建立关系
self.pt.delegate = self.lgView;

} `` 对于上述MVP`的简单案例,其实还存在很多缺陷。这种简陋的代码,连开发中常见的需求都难以满足

例如以下一些情况:

  • 遇到更复杂的Cell形态
  • 使用过多的delegate等胶水代码
  • 当遇到模块层次更深的情况,相互之间难以通讯 所以,针对上述的问题,还要介绍一下MVP的真正使用方式。