VIPER是对Uncle Bob的Clean Architecture架构的一次实践。我们先大致看下Clean Architecture。
Clean Architecture
由Uncle Bob在2011年提出的Clean Architecture,是一个平台无关的抽象架构。
源代码只能向内依赖,也就是内部不依赖外部,而外部依赖内部。这种依赖包含代码名称,或类的函数,变量或任何其他命名软件实体。
同样,在外面圈中使用的数据格式不应被内圈中使用,特别是如果这些数据格式是由外面一圈的框架生成的。我们不希望任何外圆的东西会影响内圈层。
越靠近内层,越变得抽象,越接近设计的核心。越靠近外层,越和具体的平台和实现技术相关。内层的部分完全不知道外层的存在和实现方式,代码只能从外层向内层引用,目的是为了实现层与层之间的隔离。将不同抽象程度的层进行隔离,做到了把业务规则和具体实现分离开。你可以把外层看作是内层的delegate,外层只能通过内层提供的delegate接口来使用内层。
Enterprise Business Rules
实体Entities。代表了这个软件项目的业务规则。由数据实体体现,是一些可以在不同的程序应用之间共享的数据结构。
Application Business Rules
用例Use Cases。代表了本应用所使用的一些业务规则。封装和实现了用到的业务功能,会将各种实体的数据结构转为在用例中传递的实体类,但是和具体的数据库技术或者UI无关。
Interface Adapters
接口适配层。将用例的规则和具体的实现技术进行抽象地对接,将用例中用到的实体类转为供数据库存储的格式或者供View展示的格式。类似于MVVM中把Model的数据传递给ViewModel供View显示。
右下角表示了接口适配层中不同模块间的通信方式。不同的模块在业务用例中产生关联和数据传递。Input、Output就是Use Case提供给外层的数据流动接口。
Frameworks & Drivers
库和驱动层,代表了选用的各种具体的实现技术,例如持久层使用SQLite还是Core Data,网络层使用NSURLSession、NSURLConnection还是AFNetworking等。
VIPER
VIPER设计模式就是View、Interactor、Presenter、Entity、Route的首字符缩写组成。
View 视图
- 展示界面,组合各种UIView,并在UIViewController内管理各种控件的布局、更新
- 显示Presenter告知的内容,来组织对内部的布局
- 对外暴露用户更新UI的接口,自己不主动更新UI
- 用户输入中继回Presenter
- View持有一个由外部注入的viewDataSource对象,在View的渲染过程中,会从viewDataSource获取一些用于展示的数据,viewDataSource的接口命名应该尽量和具体业务无关
- View向Presenter提供routeSource,也就是用于界面跳转的源界面
View层会引入各种自定义控件,这些控件有许多delegate,都在View层实现,统一包装后,再交给Presenter层实现。因为Presenter层并不知道View的实现细节,因此也就不知道这些控件的接口,Presenter层只知道View层统一暴露出来的接口。而且这些控件的接口在定义时可能会将数据获取、事件回调、控件渲染接口混杂起来,最具代表性的就是UITableViewDataSource里的-tableView:cellForRowAtIndexPath:。这个接口同时涉及到了UITableViewCell和渲染cell所需要的Model,是非常容易产生耦合的地方,因此需要做一次分解。应该在View的dataSource里定义一个从外部获取所需要的简单类型数据的方法,在-tableView:cellForRowAtIndexPath:里用获取到的数据渲染cell。
Presenter 表示层
Presenter由View持有。Presenter调用Interactor提供的UseCase执行业务逻辑。Presenter作为业务和View的中转站,不包含业务实现代码。而是调用各种Use Case。它的职责有:
- 接收并处理来自View的事件
- 维护和View相关的各种状态和配置,比如界面是否使用夜间模式等
- 调用Interactor提供的Use Case执行业务逻辑
- 向Interactor提供View中的数据,让Interactor生成需要的Model
- 接收并处理来自Interactor的业务事件回调事件
- 通知View进行更新操作
- 通过Wireframe跳转到其他View
Presenter是View和业务之间的中转站,它不包含业务实现代码,而是负责调用现成的各种Use Case,将具体事件转化为具体业务。Presenter里不应该导入UIKit,否则就有可能入侵View层的渲染工作。Presenter里也不应该出现Model类,当数据从Interactor传递到Presenter里时,应该转变为简单的数据结构。
Interactor 交互器
实现和封装各种业务的Use Case,可以获取各种Manager或者Service用于实现业务逻辑
- 实现和封装各种业务的Use Case,供外部调用
- 维护和业务相关的各种状态,比如是否正在编辑笔记
- Interactor可以获取各种Manager和Service,用于组合实现业务逻辑,这些Manager和Service应该是由外部注入的依赖,而不是直接引用具体的类
- 通过DataManager维护Model
- 监听各种外部的业务事件并处理,必要时将事件发送给eventHandler
- Interactor持有一个由外部注入的eventHandler对象,将需要外部处理的业务事件发送给eventHandler,或者通过eventHandler接口对某些数据操作的过程进行回调
- Interactor持有一个由外部注入的dataSource对象,用于获取View上的数据,以更新Model
Interactor是业务的实现者和维护者,它会调用各种Service来实现业务逻辑,封装成明确的用例。而这些Service在使用时,也都是基于接口的,因为Interactor的实现不和具体的类绑定,而是由Application注入Interactor需要的Service。
Entity 实体
这里是业务的Model层,是业务逻辑的实体。
Wireframe 框架
初始化整个VIPER,生成各部分的类,并设置依赖关系,并且引用另一个模块的Wireframe,负责跳转到另一个界面
1.protocol可以抽出基类
2.一般来说,viewDataSource和eventHandler都是由Presenter来担任的,Presenter接收到dataSource请求时,从Interactor里获取并返回对应的数据。也可以把tableview获取数据放在FristViewDataSource方法里.
3.如果是整个项目,设置一个总路由工厂类TMRoute,我们根据业务分成四大模块:TMARoute、TMBRoute、TMCRoute、TMDRoute 各个大模块统筹自己内部的小模块。每一个ViewController实现一个Protocol,而在相应大模块中维护所有的protocol。
简单代码示例
FristWireframe
//FristWireframe.h
@interface FristWireframe : NSObject <FristWireframeProtocol>
+ (void)pushFristVC;
@end
复制代码
//FristWireframe.m
@interface FristWireframe ()
@property (nonatomic,weak) UIViewController<FristViewDataSource> *view;
@end
@implementation FristWireframe
+ (void)pushFristVC{
UIViewController *fristVC = [self buildView];
[[UIViewController sp_topNavigationController] pushViewController:fristVC animated:YES];
}
+ (UIViewController *)buildView{
FristViewController *v = [[FristViewController alloc]init];
FristWireframe *wireframe = [[FristWireframe alloc]init];
FristPresenter *presenter = [[FristPresenter alloc]init];
FristInteractor *interactor = [[FristInteractor alloc]initWithData:nil];
//View 和 Presenter关联
v.eventHandler = presenter;
//Presenter 和 View、Wireframe、Interactor关联
presenter.view = v;
presenter.wireframe = wireframe;
presenter.interactor = interactor;
//Wireframe 和 View 关联
wireframe.view = v;
return v;
}
#pragma mark - FristWireframeProtocol
- (void)goNewVCWithIndex:(NSUInteger)index{
NSLog(@"goNewVCWithIndex");
UIViewController *vc = [[UIViewController alloc]init];
vc.title = [NSString stringWithFormat:@"new vc title:%ld",index];
vc.view.backgroundColor = [UIColor orangeColor];
[self.view.navigationController pushViewController:vc animated:YES];
}
复制代码
FristPresenter
//FristPresenter.h
@interface FristPresenter : NSObject <FristEventHandlerProtocol,EventHandleCycleProtocol>
@property (nonatomic, strong) id<FristWireframeProtocol> wireframe;
@property (nonatomic, weak) UIViewController<FristViewDataSource> *view;
@property (nonatomic, strong) id<FristInteractorProtocol> interactor;
@end
复制代码
//FristPresenter.m
#pragma mark - EventHandleCycleProtocol
- (void)event_viewWillAppear:(BOOL)animated{
if ([self.interactor respondsToSelector:@selector(fetchData:)]) {
[self.interactor fetchData:^(NSError * _Nonnull error) {
if (!error) {
__weak typeof(self) weakSelf = self;
[weakSelf.view reloadData];
}
}];
}
}
#pragma mark - FristEventHandlerProtocol
- (NSUInteger)numberRow{
return self.interactor.dataArray.count;
}
- (FristModel *)itemForIndexPath:(NSIndexPath *)indexPath{
if (indexPath.row >= self.interactor.dataArray.count) {
return nil;
}
FristModel *item = self.interactor.dataArray[indexPath.row];
return item;
}
-(void)didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.row >= self.interactor.dataArray.count) {
return;
}
NSLog(@"didSelectRowAtIndexPath:%ld",(long)indexPath.row);
if ([self.wireframe respondsToSelector:@selector(goNewVCWithIndex:)]) {
[self.wireframe goNewVCWithIndex:indexPath.row];
}
}
@end
复制代码
FristInteractor
//FristInteractor.h
@interface FristInteractor : NSObject <FristInteractorProtocol>
@end
复制代码
//FristInteractor.m
@implementation FristInteractor
@synthesize dataArray;
- (instancetype)initWithData:(NSArray<FristModel *> *)data{
if (self = [super init]) {
if (data) {
self.dataArray = data;
} else {
self.dataArray = [NSArray array];
}
}
return self;
}
- (void)fetchData:(void (^)(NSError * _Nonnull))completion{
NSMutableArray *tmp = [NSMutableArray arrayWithArray:self.dataArray];
//假数据模拟
for (int i = 0; i < 50; i++) {
FristModel *model = [FristModel new];
int num = arc4random();
model.name = [NSString stringWithFormat:@"名字:%d",num];
model.desc = [NSString stringWithFormat:@"编号:%d",num];
[tmp addObject:model];
}
self.dataArray = [tmp copy];
if (completion) {
completion(nil);
}
}
@end
复制代码
FristModel
//FristModel.h
@interface FristModel : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *desc;
@end
复制代码
//FristModel.m
@implementation FristModel
@end
复制代码
FristViewController
//FristViewController.h
@interface FristViewController : UIViewController <FristViewDataSource>
@property (nonatomic,strong) id<FristEventHandlerProtocol,EventHandleCycleProtocol> eventHandler;
@end
复制代码
//FristViewController.m
static NSString * TABLEVIEWCELL_IDENTIFIER = @"FristTableViewCell";
@interface FristViewController () <UITableViewDelegate,UITableViewDataSource>
@property (nonatomic,strong) UITableView *tableView;
@end
@implementation FristViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = @"frist";
self.view.backgroundColor = [UIColor yellowColor];
self.tableView = ({
UITableView *tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
tableView.delegate = self;
tableView.dataSource = self;
tableView.rowHeight = 50;
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
tableView.showsVerticalScrollIndicator = NO;
tableView.showsVerticalScrollIndicator = NO;
[tableView registerClass:[FristTableViewCell class]
forCellReuseIdentifier:TABLEVIEWCELL_IDENTIFIER];
[self.view addSubview:tableView];
tableView;
});
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
if ([self.eventHandler respondsToSelector:@selector(event_viewWillAppear:)]) {
[self.eventHandler event_viewWillAppear:animated];
}
}
#pragma mark - FristViewDataSource
-(void)reloadData{
[self.tableView reloadData];
}
#pragma mark - UITableViewDelegate | UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if ([self.eventHandler respondsToSelector:@selector(numberRow)]) {
return [self.eventHandler numberRow];
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
FristTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TABLEVIEWCELL_IDENTIFIER forIndexPath:indexPath];
if (!cell) {
cell = [[FristTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TABLEVIEWCELL_IDENTIFIER];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if ([self.eventHandler respondsToSelector:@selector(itemForIndexPath:)]) {
FristModel *item = [self.eventHandler itemForIndexPath:indexPath];
cell.model = item;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[UIView animateWithDuration:0.3 animations:^{
// 稍微增加cell视图的大小
cell.transform = CGAffineTransformMakeScale(2.0, 2.0);
} completion:^(BOOL finished) {
cell.transform = CGAffineTransformMakeScale(1.0, 1.0);
if ([self.eventHandler respondsToSelector:@selector(didSelectRowAtIndexPath:)]) {
[self.eventHandler didSelectRowAtIndexPath:indexPath];
}
}];
}
@end
复制代码