VIPER架构初探

VIPER是对Uncle Bob的Clean Architecture架构的一次实践。我们先大致看下Clean Architecture。

Clean Architecture

由Uncle Bob在2011年提出的Clean Architecture,是一个平台无关的抽象架构。 image.png

源代码只能向内依赖,也就是内部不依赖外部,而外部依赖内部。这种依赖包含代码名称,或类的函数,变量或任何其他命名软件实体。

同样,在外面圈中使用的数据格式不应被内圈中使用,特别是如果这些数据格式是由外面一圈的框架生成的。我们不希望任何外圆的东西会影响内圈层。

越靠近内层,越变得抽象,越接近设计的核心。越靠近外层,越和具体的平台和实现技术相关。内层的部分完全不知道外层的存在和实现方式,代码只能从外层向内层引用,目的是为了实现层与层之间的隔离。将不同抽象程度的层进行隔离,做到了把业务规则和具体实现分离开。你可以把外层看作是内层的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

image.png

扫描二维码关注公众号,回复: 13798010 查看本文章

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。

image.png

简单代码示例

image.png

image.png

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
复制代码

完整项目

参考

猜你喜欢

转载自juejin.im/post/7087935696366731278