iOS底层探索39-架构模式

475 阅读18分钟

[TOC]

经典 MVC 以及相关延伸

MVC模式(Model、View、Controller)

MVC模式(Model、View、Controller) 图片.png

经典的MVC设计模式,Controller对象拥有View和Model对象,两者通过Controller进⾏行行沟通。

包含三个角色:

Model -> 模型保存应用程序的数据。

View -> 视图是模型的可视化表示以及用户交互的控件。

Controller -> 控制器是一个协调所有工作的中介者。它访问模型中的数据并在视图中展示它们,同时它们还监听事件和操作数据。

各个角色之间的通讯方式:
Controller->Model 和 Controller->View
绿色的箭头表示直接引用。直接引用直观来说,就是说需要包含引用类的申明头文件和类的实例变量。可以看到,只有Controller中,有对Model和View的直接引用。其中对View的直接引用体现为IBOutlet。

View->Controller

设置View对应的Action Target。如设置UIButton的Touch up inside的Action Target。
设置View的delegate,如UIAlertViewDelegate,UIActionSheetDelegate等。
设置View的data source,如UITableViewDataSource。

Model->Controller
通过 Notification和KVO 来和Controller通讯。

但是实际使用过程中会出现如下的问题

图片.png

为此有一些对于MVC设计模式的延伸: 参考CDD

MVP模式(Model、View、Presenter)

MVP模式(Model、View、Presenter)

图片.png

有如下特点:

  • 任务均摊:我们将最主要的任务划分到Presenter和Model,而View的功能较少。
  • 可测试性:非常好,由于一个功能简单的View层,所以测试大多数业务逻辑也变得简单。
  • 易用性:代码量比MVC模式大,但MVP概念非常清晰。

MVP分离了view和model层,Presenter层充当了桥梁的角色,View层只负责更新界面即可
Presenter的作用:Presenter类与视图的生命周期没有任何关联、Presenter类中没有任何页面布局的代码、Presenter类有责任通过数据和状态来更新View。

  • View: 是显示数据(model)并且将用户指令(events)传送到presenter以便作用于那些数据的一个接口。View通常含有Presenter的引用。在Android开发中通常将Activity或者Fragment作为View层。
  • Model: 对于Model层也是数据层。它区别于MVC架构中的Model,在这里不仅仅只是数据模型。在MVP架构中Model它负责对数据的存取操作,例如对数据库的读写,网络的数据的请求等。
  • Presenter:对于Presenter层他是连接View层与Model层的桥梁并对业务逻辑进行处理。在MVP架构中Model与View无法直接进行交互。所以在Presenter层它会从Model层获得所需要的数据,进行一些适当的处理后交由View层进行显示。这样通过Presenter将View与Model进行隔离,使得View和Model之间不存在耦合,同时也将业务逻辑从View中抽离。

MVVM模式(Model、View、ViewModel)

MVVM模式(Model、View、ViewModel)

图片.png

益处:减少ViewController的复杂性,使得表示逻辑易于测试,兼容MVC模式,MVVM配合一个绑定机制效果最好。

它和MVP非常相似:

  • MVVM把ViewController作为View。
  • View和Model之间没有紧耦合。

特性:

  • 划分:MVVM的View比MVP的View要做的事情多一些,因为,通过设置绑定,第一个View由ViewModel来更新状态,然而第二个只需要将所有时间传递到Presenter就行了,不需要更新它的状态。
  • 可测试性:ViewModel并不持有View,这让我们可以轻松的对它进行测试,View也可以进行测试,但是它是依赖于UIKit的,你可能会忽略它。
  • 易用性:MVVM的代码量跟MVP差不多,但是在实际的app中,需要把事件从View传递到Presenter,并且要手动的更新View,如果使用绑定的话,MVVM将会瘦身不少。

VIPER

VIPER

图片.png

View: (视图) 显示Presenter告知的内容,并将用户输入中继回Presenter。

Interactor: (交互器)包含用例指定的业务逻辑。

Presenter: (表示层,也可称主持人)包含用于准备显示内容(如从Interactor接收的)和用于对用户输入做出反应(通过从Interactor请求新数据)的视图逻辑。

Entity: (实体)包含Interactor使用的基本模型对象。

Routing: (路由)包含用于描述按哪个顺序显示哪些屏幕的导航逻辑。

CDD架构(Context、Driven、Design)

CDD架构(Context、Driven、Design)

图片.png

把应用层分解成三块任务:

  • UI的展示,UI的展示通过分解UIView可以实现复杂度的分散,UI的变化则可以参考MVVM的方式,通过观察者模式(KVO)来实现。

  • 业务的处理,业务处理为了方便管理不能分散到不同的类,反而需要放到统一的地方管理,业务代码太分散会给调试带来很大的麻烦。

  • data flow,所有数据的变化可以在统一的地方被追踪。数据的流向单一清晰。

在这三块划分的前提下我们再来制定CDD要达成的目标:

  • view的展示可以被分解成若干个子view.m文件,各自管理。

  • 业务处理代码放到统一的BusinessObject.m文件来管理。

  • model的变化有统一的类DataHandler.m文件来管理。

  • UIViewController里面只存放Controller生命周期相关的代码,做成轻量级的Controller。

  • 所有子view可以处理只和自己相关的逻辑,如果属于整体的业务逻辑,则需要通过context传输到BusinessObject来处理。

  • 子view展示所需的数据可以通过context获取到,并通过KVO的方式直接进行绑定。

view和context之间耦合的方式。view产生的数据要交给BusinessObject进行处理,那么这二者之间必然要产生耦合。耦合的方式有很多种:

只通过model进行耦合,二者只需要引用相同的model就可以进行交互,MVVM里view通过KVO的方式监听model变化就是这种耦合,这种耦合最弱,但调试会麻烦一些。

通过model+protocol进行耦合。耦合的双方需要引用相同的model和protocol文件。这种方式属于面向接口编程的范畴,耦合也比较弱,但比上面的方式强。优点是调试方便,delegate的调试可以单步step into。

通过model+class进行耦合。这是我们最不愿意看到的耦合方式,类与类之间直接相互引用,任何一方变化都有可能引起潜在的bug。

view与context之间耦合的方式采用的是第二种,方便调试且耦合清晰。view会引用business object和data handler实现的相关协议,进而调用。

第二种方式本质上还是面向协议 使用了MVP的思想

本文重点探讨MVP及其思想 以及CDD架构的设计思路

轻量化VC

所谓的架构就是“以低耦合的方式分散业务复杂度”
架构意义: 高内聚 低耦合 - 谁的事情谁做
高内聚 复用
低耦合 减少相互依赖
为了不断保持项目迭代的生命周期

图片.png

传统的mvc, Controller什么都干

  • 繁重的UI
  • 啰嗦的业务逻辑
  • 很长的网络层
  • 难受的代理 ...

比如最经典的场景:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath];
    id model = [self modelsAtIndexPath:indexPath];
    cell.model = self.dataArray[indexPath.row];
    return cell;
}


//cell中 改变模型数据
- (void)setNum:(int)num{
    _num                = num;
    self.numLabel.text  = [NSString stringWithFormat:@"%d",self.num];
    self.model.num      = self.numLabel.text; // 指手画脚
}

//cell中 接收模型数据
// APP : MODEL -> UI
- (void)setModel:(Model *)model{
    _model              = model;
    self.numLabel.text  = model.num;
    self.nameLabel.text = model.name;
}

self.model.num = self.numLabel.text; 违背了架构的意义 view和model之间产生依赖,产生耦合 view具备修改和控制model的权利,产生依赖,产生耦合

为此,在构建 mvc -> mvp 的过程中
先对vc减负,做轻量化vc,抽取出present做model和view之间的桥梁并对业务逻辑进行处理
vc只做 建立依赖关系 的工作

主要工作:

自定义Presenter类处理数据加载(网络请求)、业务逻辑等工作
自定义DataSource类封装相关代理
自定义View承接繁重的ui界面

自定义Presenter类处理数据加载(网络请求)、业务逻辑等工作

#import "Present.h"

@implementation Present

- (instancetype)init{
    if (self = [super init]) {
        [self loadData];
    }
    return self;
}


- (void)loadData{
    
    NSArray *temArray =
    @[
      @{@"name":@"H",@"imageUrl":@"http://H",@"num":@"99"},
      @{@"name":@"K",@"imageUrl":@"http://K",@"num":@"99"},
      @{@"name":@"C",@"imageUrl":@"http://C",@"num":@"99"},
      @{@"name":@"Co",@"imageUrl":@"http://Co",@"num":@"59"},
      @{@"name":@"Li",@"imageUrl":@"http://Li",@"num":@"24"},
    @{@"name":@"婷",@"imageUrl":@"http://婷",@"num":@"12"},
    @{@"name":@"小",@"imageUrl":@"http://小",@"num":@"17"},];

    for (int i = 0; i<temArray.count; i++) {
        Model *m = [Model modelWithDictionary:temArray[i]];
        [self.dataArray addObject:m];
    }
}

#pragma mark - lazy

- (NSMutableArray *)dataArray{
    if (!_dataArray) {
        _dataArray = [NSMutableArray arrayWithCapacity:10];
    }
    return _dataArray;
}

@end

自定义DataSource类封装相关代理

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

typedef void (^CellConfigureBefore)(id cell, id model, NSIndexPath * indexPath);


@interface LMDataSource : NSObject<UITableViewDataSource,UICollectionViewDataSource>

@property (nonatomic, strong)  NSMutableArray *dataArray;;

//自定义
- (id)initWithIdentifier:(NSString *)identifier configureBlock:(CellConfigureBefore)before;

//sb
@property (nonatomic, strong) IBInspectable NSString *cellIdentifier;

@property (nonatomic, copy) CellConfigureBefore cellConfigureBefore;



- (void)addDataArray:(NSArray *)datas;

- (id)modelsAtIndexPath:(NSIndexPath *)indexPath;

@end

#import "LMDataSource.h"

@implementation LMDataSource

- (id)initWithIdentifier:(NSString *)identifier configureBlock:(CellConfigureBefore)before {
    if(self = [super init]) {
        _cellIdentifier = identifier;
        _cellConfigureBefore = [before copy];
    }
    return self;
}


- (void)addDataArray:(NSArray *)datas{
    if(!datas) return;
    
    if (self.dataArray.count>0) {
        [self.dataArray removeAllObjects];
    }

    [self.dataArray addObjectsFromArray:datas];
    
}

- (id)modelsAtIndexPath:(NSIndexPath *)indexPath {
    return self.dataArray.count > indexPath.row ? self.dataArray[indexPath.row] : nil;
}



#pragma mark UITableViewDataSource


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return !self.dataArray  ? 0: self.dataArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath];
    id model = [self modelsAtIndexPath:indexPath];
    if(self.cellConfigureBefore) {
        self.cellConfigureBefore(cell, model,indexPath);
    }
    return cell;
}


#pragma mark UICollectionViewDataSource


- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return !self.dataArray  ? 0: self.dataArray.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:self.cellIdentifier forIndexPath:indexPath];
    id model = [self modelsAtIndexPath:indexPath];
    
    if(self.cellConfigureBefore) {
        self.cellConfigureBefore(cell, model,indexPath);
    }

    return cell;
}


- (NSMutableArray *)dataArray{
    
    if (!_dataArray) {
        _dataArray = [NSMutableArray arrayWithCapacity:5];
    }
    return _dataArray;
    
}

@end

自定义view承接繁重的ui界面

#import "MVCView.h"
#import "MVCTableViewCell.h"

static NSString *const reuserId = @"reuserId";

@implementation MVCView

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        
        [self addSubview:self.tableView];
    }
    return self;
}

- (void)setDataSource:(id<UITableViewDataSource,UICollectionViewDataSource>)dataSource {
    self.tableView.dataSource = dataSource;
}

- (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:reuserId];
    }
    return _tableView;
}

@end

最后,轻量级vc中的代码可以简化为以下写法

#import "MVCViewController.h"
#import "LMDataSource.h"
#import "MVCTableViewCell.h"
#import "Present.h"
#import "Model.h"
#import <YYKit.h>
#import "MVCView.h"

static NSString *const reuserId = @"reuserId";

@interface MVCViewController ()

@property (nonatomic, strong) LMDataSource      *dataSource;
@property (nonatomic, strong) Present           *pt;
@property (nonatomic, strong) MVCView *mvcView;

@end

@implementation MVCViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view = self.mvcView;
    
    // 1.数据提供层
    self.pt = [[Present alloc] init];
    
    // 2.数据业务处理层
    __weak typeof(self) weakSelf = self;
    self.dataSource = [[LMDataSource alloc] initWithIdentifier: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;
    }];
    
    [self.dataSource addDataArray:self.pt.dataArray];
    
    // 3.建立关系
    [self.mvcView setDataSource:self.dataSource];    
}

#pragma mark - lazy
- (MVCView *)mvcView {
    if (!_mvcView) {
        _mvcView = [[MVCView alloc]initWithFrame:self.view.bounds];
    }
    return _mvcView;
}

MVP构建

至于view和model之间的耦合问题
Present 解决
主要处理 双方之间数据交互的过程,实现双向通信

model->presenter->view

获取到model数据在view上显示的过程

mvc时代耦合 model->view

// cell赋值过程  
// mvc时代耦合 model->view
- (void)setModel:(Model *)model{
    _model              = model;
    self.numLabel.text  = model.num;
    self.nameLabel.text = model.name;
}

mvp轻量化vc之后在vc中做 pt取值 dataSource赋值 model->presenter->view

// 轻量化vc之后在vc中做 pt取值 dataSource赋值  model->presenter->view
// 2.数据业务处理层
    __weak typeof(self) weakSelf = self;
    self.dataSource = [[LMDataSource alloc] initWithIdentifier: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;
    }];
    [self.dataSource addDataArray:self.pt.dataArray];

view->presenter->model

点击view上的按钮 触发model数据改变的过程

mvc时代又是耦合 view->model

// 点击按钮改变model的过程
// mvc时代又是耦合    view->model
- (void)didClickAddBtn:(UIButton *)sender{
    if ([self.numLabel.text intValue]>=200) {return;}
    self.num++;
}
#pragma mark - setter
// UI 响应 -> model
- (void)setNum:(int)num{
    _num                = num;
    self.numLabel.text  = [NSString stringWithFormat:@"%d",self.num];
    self.model.num      = self.numLabel.text; // 指手画脚
}

mvp之后基于协议做UI响应 view->presenter->model

//mvp之后基于协议做UI响应  view->presenter->model
@protocol PresentDelegate <NSObject>
// 需求: UI num -> model
- (void)didClickNum:(NSString *)num indexpath:(NSIndexPath *)indexpath;
@end

在presenter中实现PresentDelegate协议方法

#import#import "Present.h"

@implementation Present

- (instancetype)init{
    if (self = [super init]) {
        [self loadData];
    }
    return self;
}


- (void)loadData{
    
    NSArray *temArray =
    @[
      @{@"name":@"H",@"imageUrl":@"http://H",@"num":@"99"},
      @{@"name":@"K",@"imageUrl":@"http://K",@"num":@"99"},
      @{@"name":@"C",@"imageUrl":@"http://C",@"num":@"99"},
      @{@"name":@"Co",@"imageUrl":@"http://Co",@"num":@"59"},
      @{@"name":@"Li",@"imageUrl":@"http://Li",@"num":@"24"},
    @{@"name":@"婷",@"imageUrl":@"http://婷",@"num":@"12"},
    @{@"name":@"小",@"imageUrl":@"http://小",@"num":@"17"},];

    for (int i = 0; i<temArray.count; i++) {
        Model *m = [Model modelWithDictionary:temArray[i]];
        [self.dataArray addObject:m];
    }
}

#pragma mark -PresentDelegate
- (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

cell 中点击按钮之后调用代理方法即可

- (void)didClickAddBtn:(UIButton *)sender{
    if ([self.numLabel.text intValue]>=200) {return;}
    self.num++;
}
#pragma mark - setter
// UI 响应 -> model
- (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];
    }
}

vc中代码不变

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view = self.mvcView;
    
    // 1.数据提供层
    self.pt = [[Present alloc] init];
    
    // 2.数据业务处理层
    __weak typeof(self) weakSelf = self;
    self.dataSource = [[LMDataSource alloc] initWithIdentifier: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;
    }];
    
    [self.dataSource addDataArray:self.pt.dataArray];
    
    // 3.建立关系
    [self.mvcView setDataSource:self.dataSource];
}

双向绑定 view<->presenter<->model

有时候需求会增加,例如点击cell 当num>5 的时候只展示前几条数据
这时候 只需添加新的协议方法即可

@protocol PresentDelegate <NSObject>
// 需求: UI num -> model
- (void)didClickNum:(NSString *)num indexpath:(NSIndexPath *)indexpath;
- (void)reloadUI;
- (void)reloadUI:(NSArray*)data;//涉及到数据改变
@end

- (void)didClickNum:(NSString *)num indexpath:(NSIndexPath *)indexpath; 方法中做判断并处理数据源
- (void)reloadUI;方法刷新页面

present在- (void)didClickNum:(NSString *)num indexpath:(NSIndexPath *)indexpath; 方法中处理数据源 并且调用代理方法- (void)reloadUI;刷新页面


#pragma mark -PresentDelegate
- (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":@"H",@"imageUrl":@"http://H",@"num":@"99"},
                  @{@"name":@"K",@"imageUrl":@"http://K",@"num":@"99"},
                  @{@"name":@"C",@"imageUrl":@"http://C",@"num":@"99"},
                  @{@"name":@"Co",@"imageUrl":@"http://Co",@"num":@"59"},
                  @{@"name":@"Li",@"imageUrl":@"http://Li",@"num":@"24"},
                @{@"name":@"婷",@"imageUrl":@"http://婷",@"num":@"12"},
                @{@"name":@"小",@"imageUrl":@"http://小",@"num":@"17"},];

                for (int i = 0; i<temArray.count; i++) {
                    Model *m = [Model modelWithDictionary:temArray[i]];
                    [self.dataArray addObject:m];
                }
                
                // model - delegate -> UI
                if (self.delegate && [self.delegate respondsToSelector:@selector(reloadUI)]) {
                    [self.delegate reloadUI];
                }
                
                if (self.delegate && [self.delegate respondsToSelector:@selector(reloadUI:)]) {
                    [self.delegate reloadUI:self.dataArray];
                }
                
            }
            
        }
    }
}

view实现刷新UI的方法


#import "MVCView.h"
#import "MVCTableViewCell.h"
#import "LMDataSource.h"

static NSString *const reuserId = @"reuserId";

@implementation MVCView

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        
        [self addSubview:self.tableView];
    }
    return self;
}

- (void)setDataSource:(id<UITableViewDataSource,UICollectionViewDataSource>)dataSource {
    self.tableView.dataSource = dataSource;
}

#pragma mark -PresentDelegate
- (void)reloadUI{
    [self.tableView reloadData];
}

//涉及数据改变的情况
- (void)reloadUI:(NSArray *)data {
    LMDataSource *dataSource = self.tableView.dataSource;
    [dataSource addDataArray:data];
    [self.tableView reloadData];
}
#pragma mark - lazy
- (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:reuserId];
    }
    return _tableView;
}

@end

vc中实现代理传递 self.pt.delegate = self.mvcView;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view = self.mvcView;
    
    // 1.数据提供层
    self.pt = [[Present alloc] init];
    
    // 2.数据业务处理层
    __weak typeof(self) weakSelf = self;
    self.dataSource = [[LMDataSource alloc] initWithIdentifier: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;
    }];
    
    [self.dataSource addDataArray:self.pt.dataArray];
    
    // 3.建立关系
    [self.mvcView setDataSource:self.dataSource];
    self.pt.delegate = self.mvcView;
}

综上 实现了 view<->presenter<->model 的双向绑定过程

MVP思想总结

基于协议实现了view和model的双向绑定过程
在开发过程中
产品-> 需求-> 接口-> 代理三部曲
mvp 面向协议的特点可以解决mvvm的问题:
mvvm 更多面向block 随着嵌套加深 很难定位问题

mvp点对点形式 使项目更清晰明了
新的需求来之后 只需添加对应协议方法 实现对应的协议方法即可 ,代码思路更加清晰,维护起来也更加容易

适配器设计

以上的mvp 也存在一些问题

  1. 简单的demo使用此方式的mvp还可以 但是当cell种类增多的情况,
  2. 频繁使用delegate ,协议胶水代码非常之多
  3. UI嵌套层次过深的情况 相互之间的通信就会比较困难,比如直播间礼物视图 点击送礼物 ...

1.设计适配器 处理cell种类增多的情况

使用 功能定制化 的思路进行适配器设置
HomeAdapter 实现具体的页面cell适配
抽取BaseAdapter 做通用的基础的功能 代理方法的实现 基本的赋值过程等 实现去中心化 达到复用效果
HomeAdapter : BaseAdapter

BaseAdapter基类 做通用的基础的功能 代理方法的实现 基本的赋值过程等 实现去中心化 达到复用效果

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@protocol KCBaseAdapterDelegate <NSObject>

@optional
- (void)didSelectCellData:(id)cellData;
- (void)deleteCellData:(id)cellData;
- (void)willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;

@end


@protocol KCBaseAdapterScrollDelegate <NSObject>

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView ;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView ;
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView;

@end

@protocol KCBaseAdapterPullUpDelegate <NSObject>

- (void)beginToRefresh;

@end

@interface KCBaseAdapter : NSObject<UITableViewDataSource, UITableViewDelegate>
{
    
}

@property (nonatomic, weak) id<KCBaseAdapterDelegate>   adapterDelegate;
@property (nonatomic, weak) id<KCBaseAdapterScrollDelegate>              adapterScrollDelegate;
@property (nonatomic, weak) id<KCBaseAdapterPullUpDelegate>              adapterPullUpDelegate;

- (float)getTableContentHeight;
- (void)refreshCellByData:(id)data tableView:(UITableView*)tableView;

- (NSArray*)getAdapterArray;
- (void)setAdapterArray:(NSArray*)arr;


@end

@interface KCBaseAdapter ()
@property (nonatomic, strong) NSMutableArray    *arr;
@end

@implementation KCBaseAdapter

- (instancetype)init
{
    self = [super init];
    if (self) {
        _arr = [NSMutableArray new];
    }
    return self;
}

- (NSArray*)getAdapterArray
{
    return _arr;
}

- (void)setAdapterArray:(NSArray*)arr
{
    [_arr removeAllObjects];
    [_arr addObjectsFromArray:arr];
}

- (float)getTableContentHeight
{
    return 0;
}

#pragma mark - 抽象方法
- (void)refreshCellByData:(id)data tableView:(UITableView*)tableView
{
    
}

#pragma mark    UITableView DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return _arr.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row > self.getAdapterArray.count-1) {
        return [UITableViewCell new];
    }
    
    id cellData = [self.arr objectAtIndex:indexPath.row];
    
    UITableViewCell* cell = NULL;
    CCSuppressPerformSelectorLeakWarning(
                                         cell = [self performSelector:NSSelectorFromString([NSString stringWithFormat:@"tableView:cellFor%@:", [cellData class]]) withObject:tableView withObject:cellData];);
    
    return cell;
}

#pragma mark - UITableView Delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 0;
}


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row > self.getAdapterArray.count-1) {
        return;
    }
    
    [tableView deselectRowAtIndexPath:indexPath animated:false];
    
    id cellData = [self.arr objectAtIndex:indexPath.row];
    if (self.adapterDelegate) {
        if ([_adapterDelegate respondsToSelector:@selector(didSelectCellData:)]) {
            [_adapterDelegate didSelectCellData:cellData];
        }
    }
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row > self.getAdapterArray.count-1) {
        return;
    }
    
    if (self.adapterPullUpDelegate && [self.adapterPullUpDelegate respondsToSelector:@selector(beginToRefresh)]) {
        //倒数第三个 发送请求
        if (tableView.style == UITableViewStyleGrouped) {
            if (self.getAdapterArray.count >1) {
                NSArray *dataArray = [self.getAdapterArray objectAtIndex:0];
                if (dataArray.count > 4 && dataArray.count - 4 == indexPath.row) {
                    [self.adapterPullUpDelegate beginToRefresh];
                }
            }
        }
        else if (tableView.style == UITableViewStylePlain)
        {
            if (self.getAdapterArray.count > 4 && self.getAdapterArray.count - 4 == indexPath.row) {
                [self.adapterPullUpDelegate beginToRefresh];
            }
        }
    }
    if (self.adapterDelegate) {
        if ([_adapterDelegate respondsToSelector:@selector(willDisplayCell:forRowAtIndexPath:)]) {
            [_adapterDelegate willDisplayCell:cell forRowAtIndexPath:indexPath];
        }
    }
}

#pragma mark - UIScrollViewDelegate

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    
    if (self.adapterScrollDelegate && [self.adapterScrollDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) {
        [self.adapterScrollDelegate scrollViewWillBeginDragging:scrollView];
    }
    
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (self.adapterScrollDelegate && [self.adapterScrollDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
        [self.adapterScrollDelegate scrollViewDidScroll:scrollView];
    }
}


- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (self.adapterScrollDelegate && [self.adapterScrollDelegate respondsToSelector:@selector(scrollViewDidEndDragging:)]) {
        [self.adapterScrollDelegate scrollViewDidEndDragging:scrollView];
    }
}



@end

HomeAdapter 实现具体的页面cell适配 HomeAdapter : BaseAdapter

#import "KCHomeAdapter.h"
#import "KCHomeTableViewCell.h"
#import "KCChannelProfile.h"

@implementation KCHomeAdapter

- (CGFloat)getCellHeight:(NSInteger)row
{
    CGFloat height = SCREEN_WIDTH*608/1080 + 54;
    KCChannelProfile *model = [self.getAdapterArray objectAtIndex:row];
    if (model.title.length > 0) {
        CGSize titleSize = [model.title sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14]}];
        if (titleSize.width > SCREEN_WIDTH - 35) {
            // 两行
            height +=67;
        }else{
            height +=50;
        }
    }else{
        height += 8;
    }
    return height;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    return [self getCellHeight:indexPath.row];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.getAdapterArray.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    KCChannelProfile* liveModel = self.getAdapterArray[indexPath.row];
    
    UITableViewCell *cell = nil;
    
    CCSuppressPerformSelectorLeakWarning (
                                          cell = [self performSelector:NSSelectorFromString([NSString stringWithFormat:@"tableView:cellForKCChannelProfile:"]) withObject:tableView withObject:liveModel];
                                          );
    return cell;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForKCChannelProfile:(id)model {
    NSString *cellIdentifier = NSStringFromSelector(_cmd);
    KCHomeTableViewCell *cell = (KCHomeTableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell) {
        cell = [[KCHomeTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
    }
    
    KCChannelProfile* liveModel = model;
    [cell setCellContent:liveModel];
    
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:NO];
    id model = self.getAdapterArray[indexPath.row];
    if (self.adapterDelegate && [self.adapterDelegate respondsToSelector:@selector(didSelectCellData:)]) {
        [self.adapterDelegate didSelectCellData:model];
    }
}

@end

2.对于胶水代码,使用宏定义做代理方法胶水代码的轻量化调用

发送消息的方式 instance响应message

#define KC(instance, protocol, message) [(id<protocol>)(instance) message]

3.设计context 处理复杂页面UI交互 ,比如直播间礼物视图 点击送礼物

// 适配器设计
    self.homeAdapter = [[KCHomeAdapter alloc] init];
    // 消息接受者 + 主体
//    KC(self.homeView, KCHomeViewdelegate, buildView);//解决胶水代码
    
    //UI搭建界面
    KC(self.homeView, KCHomePresentDelegate, buildHomeView:self.homeAdapter);
    //请求数据
    KC(self.context.presenter,KCHomePresentDelegate, loadDataWithAdapter:self.homeAdapter);
    //建立数据和UI
    [self.homeAdapter setAdapterArray:self.context.presenter.dataArray];
    KC(self.homeView, KCHomePresentDelegate, reloadUIWithData);

最终演化成下面这种方式

// 适配器设计
    self.homeAdapter = [[KCHomeAdapter alloc] init];
    // 消息接受者 + 主体
//    KC(self.homeView, KCHomeViewdelegate, buildView);//解决胶水代码
    
    //UI搭建界面
    KC(self.context.view, KCHomePresentDelegate, buildHomeView:self.homeAdapter);
    //请求数据
    KC(self.context.presenter,KCHomePresentDelegate, loadDataWithAdapter:self.homeAdapter);
    //建立数据和UI
    [self.homeAdapter setAdapterArray:self.context.presenter.dataArray];
    KC(self.context.view, KCHomePresentDelegate, reloadUIWithData);

context设计

适配器设计 context做中间者
context做信息的综合管理 不做具体的业务
这个功能放到BaseViewController中,BaseViewController中的rootContext管理对应的presenter、interactor、view等
rootContext 又赋值给contenxt 通过强弱共舞的方式解决循环引用 保证context的生命周期
self ->(weak) context ->(strong) rootContext -> self 维持context生命周期

BaseViewController中的rootContext 即为context, 管理对应的presenter、interactor、view创建等,并且对该层级的context进行赋值
对应的presenter、interactor、view的名称要和代码命名规则一致

context ,通过context可以访问presenter、interactor、view等 只具备调起的权利,不具备做业务细节的能力


#import "KCBaseViewController.h"

@interface KCBaseViewController : ViewController

@property (nonatomic, strong) CDDContext    *rootContext;
- (void)configMVP:(NSString*)name;

@end

@interface KCBaseViewController ()
@property (nonatomic, strong) NSMutableDictionary   *eventMap;
@property (nonatomic, assign) BOOL                  mvpEnabled;
@end

@implementation KCBaseViewController

- (void)configMVP:(NSString*)name
{
    self.mvpEnabled = true;
    
    // context做信息的综合管理 不做具体的业务
    // self ->(weak) context ->(strong) rootContext -> self  维持context生命周期
    self.rootContext = [[CDDContext alloc] init]; //strong
    self.context = self.rootContext; //weak
    
    //presentor
    Class presenterClass = NSClassFromString([NSString stringWithFormat:@"KC%@Presenter", name]);
    if (presenterClass != NULL) { // 缓存
        self.context.presenter = [presenterClass new];
        self.context.presenter.context = self.context;//对该层级的conext进行赋值
    }
    
    //interactor
    Class interactorClass = NSClassFromString([NSString stringWithFormat:@"KC%@Interactor", name]);
    if (interactorClass != NULL) {
        self.context.interactor = [interactorClass new];
        self.context.interactor.context = self.context;//对该层级的conext进行赋值
    }
    
    //view
    Class viewClass = NSClassFromString([NSString stringWithFormat:@"KC%@View", name]);
    if (viewClass != NULL) {
        self.context.view = [viewClass new];
        self.context.view.context = self.context;//对该层级的conext进行赋值
    }
    
    //build relation  建立关联
    self.context.presenter.view = self.context.view;
    self.context.presenter.baseController = self;
    
    self.context.interactor.baseController = self;
    
    self.context.view.presenter = self.context.presenter;
    self.context.view.interactor = self.context.interactor;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    if (self.mvpEnabled) {
        self.context.view.frame = self.view.bounds;
        self.view = self.context.view;
    }
    
    KCLog(@"\n\nDid Load ViewController: %@\n\n", [self class]);
}

- (void)dealloc
{
    KCLog(@"\n\nReleasing ViewController: %@\n\n", [self class]);
    
    [Notif removeObserver:self];
}

@end

context的定义 包含 presenter、interactor、view等

#import <Foundation/Foundation.h>
#import "NSObject+CDD.h"

@class CDDContext;
@class CDDView;

@interface CDDPresenter : NSObject
@property (nonatomic, weak) UIViewController*           baseController;
@property (nonatomic, weak) CDDView*                    view;
@property (nonatomic, weak) id                          adapter; //for tableview adapter

@end

@interface CDDInteractor : NSObject
@property (nonatomic, weak) UIViewController*           baseController;
@end


@interface CDDView : UIView
@property (nonatomic, weak) CDDPresenter*               presenter;
@property (nonatomic, weak) CDDInteractor*              interactor;
@end



//Context bridges everything automatically, no need to pass it around manually
@interface CDDContext : NSObject

@property (nonatomic, strong) CDDPresenter*           presenter;
@property (nonatomic, strong) CDDInteractor*          interactor;
@property (nonatomic, strong) CDDView*                view; //view holds strong reference back to context

@end
#import "CDDContext.h"

@implementation CDDPresenter
@end

@implementation CDDInteractor
@end

@implementation CDDView
- (void)dealloc
{
    self.context = nil;
}
@end

@implementation CDDContext

- (void)dealloc
{
    NSLog(@"context being released");
}

@end

context分发

self.context.presenter.context = self.context;//对该层级的conext进行赋值
self.context.interactor.context = self.context;//对该层级的conext进行赋值
self.context.view.context = self.context;//对该层级的conext进行赋值
    //presentor
    Class presenterClass = NSClassFromString([NSString stringWithFormat:@"KC%@Presenter", name]);
    if (presenterClass != NULL) { // 缓存
        self.context.presenter = [presenterClass new];
        self.context.presenter.context = self.context;//对该层级的conext进行赋值
    }
    
    //interactor
    Class interactorClass = NSClassFromString([NSString stringWithFormat:@"KC%@Interactor", name]);
    if (interactorClass != NULL) {
        self.context.interactor = [interactorClass new];
        self.context.interactor.context = self.context;//对该层级的conext进行赋值
    }
    
    //view
    Class viewClass = NSClassFromString([NSString stringWithFormat:@"KC%@View", name]);
    if (viewClass != NULL) {
        self.context.view = [viewClass new];
        self.context.view.context = self.context;//对该层级的conext进行赋值
    }

BaseViewController中设置了context对应的 presenter 、interactor、view,他们对应的context 也有了
self.context.view.context = self.context;
这句话保证了vc的view有context

但是直播间礼物页面是vc view的子view, 它是如何拥有context的呢?
在子view层为什么有context
直播间礼物视图 点击送礼物发礼物的过程

- (void)buttonSendClicked:(id)sender
{
//    KC(self.context.view, IStreamView, showGiftSelectionView:false);
    // streamView  子视图
    // present vc view
    // vc context
    // 子视图 享有cotext
    if (_selectedItem) {
        KC(self.context.presenter, KCLiveStreamPresenterDeleagte, sendGiftWithIndex:(int)_selectedItem.tag);
    }
}

查看context的创建过程 ,实际上context是在NSObject的CDD分类里面创建的,保证万物皆有context
在子view层为什么有context?
在get方法中 判断如果当前对象curContext为空 并且 是UIView对象,则遍历当前view的所有父类容器,如果父类有context则赋值给当前view,父类有的话 子子孙孙都会有
再结合BaseViewController对于self.view.context的赋值保证self.view有context,所以self.view的所有子视图都可以保证有context
都可以通过context做具体的业务处理

     //view
    Class viewClass = NSClassFromString([NSString stringWithFormat:@"KC%@View", name]);
    if (viewClass != NULL) {
        self.context.view = [viewClass new];
        self.context.view.context = self.context;//对该层级的conext进行赋值
    }


- (void)viewDidLoad
{
    [super viewDidLoad];
    
    if (self.mvpEnabled) {
        self.context.view.frame = self.view.bounds;
        self.view = self.context.view;
    }
    
    KCLog(@"\n\nDid Load ViewController: %@\n\n", [self class]);
}
#import "NSObject+CDD.h"
#import "CDDContext.h"
#import <objc/runtime.h>

@implementation NSObject (CDD)
@dynamic context;

- (void)setContext:(CDDContext*)object {
    objc_setAssociatedObject(self, @selector(context), object, OBJC_ASSOCIATION_ASSIGN);
}

- (CDDContext*)context {
    id curContext = objc_getAssociatedObject(self, @selector(context));
    if (curContext == nil && [self isKindOfClass:[UIView class]]) {
        
        //try get from superview, lazy get
        UIView* view = (UIView*)self;
        
        UIView* sprView = view.superview;
        while (sprView != nil) {
            if (sprView.context != nil) {
                curContext = sprView.context;//父类有context 子子孙孙都会有
                break;
            }
            sprView = sprView.superview;
        }
        
        if (curContext != nil) {
            [self setContext:curContext];
        }
    }
    
    return curContext;
}

+ (void)swizzleInstanceSelector:(SEL)oldSel withSelector:(SEL)newSel
{
    Method oldMethod = class_getInstanceMethod(self, oldSel);
    Method newMethod = class_getInstanceMethod(self, newSel);
    if (!oldMethod || !newMethod)
    {
        return;
    }
    
    class_addMethod(self,
                    oldSel,
                    class_getMethodImplementation(self, oldSel),
                    method_getTypeEncoding(oldMethod));
    class_addMethod(self,
                    newSel,
                    class_getMethodImplementation(self, newSel),
                    method_getTypeEncoding(newMethod));
    
    method_exchangeImplementations(class_getInstanceMethod(self, oldSel),
                                   class_getInstanceMethod(self, newSel));
}

@end