iOS之MVP架构模式
一起养成写作习惯!这是我参与「掘金日新计划 · 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
@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
@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
@protocol LGProtocol
-
(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
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,将
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
的真正使用方式。