iOS之MVP架构模式

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情

MVP面向协议编程,即Model View Presenter(模型 视图 协调器)。

特征

  • 任务均摊:将最主要的任务划分到PresenterModel,而View的功能较少
  • 可测试性:由于一个功能简单的View层,所以测试数业务逻辑变得简单
  • 易用性:比使用MVC模式的代码量更多,但MVP模式具备非常清晰的结构

所以,MVCMVP的区别就是,在MVPModelView没有直接通信。

设计模式

  • Model层不仅仅是创建一个数据对象,还应该包含网络请求,以及数据Cache的操作
  • View层就是一些封装、重用
  • Presenter层并不涉及数据的网络请求和Cache操作,只是搭建Model层和View层的一个桥梁

优势

  • 模型与视图完全分离,修改视图而不影响模型
  • 可以更高效地使用模型,因为所有交互都在Presenter
  • 把逻辑放在Presenter中,可以脱离用户接口进行单元测试
  • 可以将一个Presener用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁

UIModel解耦

创建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中,删除UIModel的数据绑定。在按钮点击的交互方法中,调用delegatedidClickNum方法

- (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代码中LMDataSourceBlock,完成CellUIModel的数据绑定,以及delegateindexPath的设置

#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进行双向绑定。例如:触发CelldidClickAddBtn事件,当num++大于5UI层只展示前两条数据

修改LGProtocol,增加UI刷新的事件接口

#import <Foundation/Foundation.h>

@protocol LGProtocol <NSObject>

- (void)didClickNum:(NSString *)num indexpath:(NSIndexPath *)indexpath;

- (void)reloadUI;

@end
复制代码

修改Present中的didClickNum方法,增加num大于5,只保留前两条数据,并调用delegatereloadUI方法刷新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,将UIModel建立关系

- (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的真正使用方式。

猜你喜欢

转载自juejin.im/post/7086831020703645710
今日推荐