[TOC]
经典 MVC 以及相关延伸
MVC模式(Model、View、Controller)
MVC模式(Model、View、Controller)
经典的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通讯。
但是实际使用过程中会出现如下的问题
为此有一些对于MVC设计模式的延伸: 参考CDD
MVP模式(Model、View、Presenter)
MVP模式(Model、View、Presenter)
有如下特点:
- 任务均摊:我们将最主要的任务划分到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)
益处:减少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
View: (视图) 显示Presenter告知的内容,并将用户输入中继回Presenter。
Interactor: (交互器)包含用例指定的业务逻辑。
Presenter: (表示层,也可称主持人)包含用于准备显示内容(如从Interactor接收的)和用于对用户输入做出反应(通过从Interactor请求新数据)的视图逻辑。
Entity: (实体)包含Interactor使用的基本模型对象。
Routing: (路由)包含用于描述按哪个顺序显示哪些屏幕的导航逻辑。
CDD架构(Context、Driven、Design)
CDD架构(Context、Driven、Design)
把应用层分解成三块任务:
-
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
所谓的架构就是“以低耦合的方式分散业务复杂度”
架构意义: 高内聚 低耦合 - 谁的事情谁做
高内聚 复用
低耦合 减少相互依赖
为了不断保持项目迭代的生命周期
传统的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 也存在一些问题
- 简单的demo使用此方式的mvp还可以 但是当cell种类增多的情况,
- 频繁使用delegate ,协议胶水代码非常之多
- 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