一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情。
MVP
面向协议编程,即Model View Presenter
(模型 视图 协调器)。
特征
- 任务均摊:将最主要的任务划分到
Presenter
和Model
,而View
的功能较少 - 可测试性:由于一个功能简单的
View
层,所以测试数业务逻辑变得简单 - 易用性:比使用
MVC
模式的代码量更多,但MVP
模式具备非常清晰的结构
所以,MVC
与MVP
的区别就是,在MVP
中Model
和View
没有直接通信。
设计模式
Model
层不仅仅是创建一个数据对象,还应该包含网络请求,以及数据Cache
的操作View
层就是一些封装、重用Presenter
层并不涉及数据的网络请求和Cache
操作,只是搭建Model
层和View
层的一个桥梁
优势
- 模型与视图完全分离,修改视图而不影响模型
- 可以更高效地使用模型,因为所有交互都在
Presenter
中 - 把逻辑放在
Presenter
中,可以脱离用户接口进行单元测试 - 可以将一个
Presener
用于多个视图,而不需要改变Presenter
的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁
UI
与Model
解耦
创建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
中,删除UI
对Model
的数据绑定。在按钮点击的交互方法中,调用delegate
的didClickNum
方法
- (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
代码中LMDataSource
的Block
,完成Cell
中UI
对Model
的数据绑定,以及delegate
和indexPath
的设置
#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进行双向绑定。例如:触发Cell
的didClickAddBtn
事件,当num++
大于5
,UI
层只展示前两条数据
修改LGProtocol
,增加UI
刷新的事件接口
#import <Foundation/Foundation.h>
@protocol LGProtocol <NSObject>
- (void)didClickNum:(NSString *)num indexpath:(NSIndexPath *)indexpath;
- (void)reloadUI;
@end
复制代码
修改Present
中的didClickNum
方法,增加num
大于5
,只保留前两条数据,并调用delegate
的reloadUI
方法刷新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
,将UI
和Model
建立关系
- (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
的真正使用方式。