一起养成写作习气!这是我参与「日新方案 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 <Foundation/Foundation.h>
@protocol LGProtocol <NSObject>
- (void)didClickNum:(NSString *)num indexpath:(NSIndexPath *)indexpath;
@end

Present中,完结LGProtocol协议的didClickNum办法

#import <Foundation/Foundation.h>
#import "Model.h"
#import "LGProtocol.h"
#import <YYKit.h>
@interface Present : NSObject<LGProtocol>
@property (nonatomic, strong) NSMutableArray    *dataArray;
@property (nonatomic, weak) id<LGProtocol>       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 <Foundation/Foundation.h>
@protocol LGProtocol <NSObject>
- (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 <UIKit/UIKit.h>
#import "MVCTableViewCell.h"
#import "LGProtocol.h"
@interface LGView : UIView<LGProtocol>
@property (nonatomic, strong) UITableView       *tableView;
- (void)setDataSource:(id<UITableViewDataSource>)dataSource;
@end
@implementation LGView
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self addSubview:self.tableView];
    }
    return self;
}
- (void)setDataSource:(id<UITableViewDataSource>)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的真实运用方式。