1. MVC架构

MVC属于苹果推荐的架构模式,包含几个明显的特征:
image.png

  • Model:模型层,封装了应用程序的数据,并定义操控和处理该数据的逻辑和运算

  • View:视图层,用于视图控件的渲染,并且对用户的操作作出响应

  • Controller:程序的视图对象和模型对象之间的交互和通讯,由控制器充当媒介,负责二者的执行设置和协调任务,并管理它们的生命周期

这种方式在实际开发中,会产生两个常见的问题:

  • Controller中存在大量的代理、逻辑、UI和内部方法,导致VC层过重

  • ViewModel之间很容易产生依赖,导致耦合度过高

开发中,非常典型的ViewModel产生依赖的代码:
image.png

一个好的架构,应该做到高内聚,低耦合。让代码的复用性提高,模块之间的依赖程度降低。简单来说,应该谁的事情谁做,模块之间各负其职,持续保持项目的生命周期

2. 轻量化VC

VC的实际意义,只是建立模块之间的依赖关系。但开发中,我们在VC中还会加入一些繁重的UI代码、啰嗦的业务逻辑、很长的网络层代码、难受的代理功能

这些原本不属于VC的业务代码,全部堆积在VC中,导致VC愈发笨重,难以维护

构建轻量级VC,把不属于VC的业务代码全部分散。例如:将常见的UITableView相关代理,使用自定义DataSource进行封装。将繁重的UI代码,使用自定义View进行封装

自定义DataSource封装UITableView相关代理,这个类对整个项目来说都可以被复用

  1. #import <Foundation/Foundation.h>
  2. #import <UIKit/UIKit.h>
  3. typedef void (^CellConfigureBefore)(id cell, id model, NSIndexPath * indexPath);
  4. @interface LMDataSource : NSObject<UITableViewDataSource,UICollectionViewDataSource>
  5. @property (nonatomic, weak) NSArray *dataArray;
  6. - (id)initWithIdentifier:(NSString *)identifier configureBlock:(CellConfigureBefore)before;
  7. @property (nonatomic, strong) IBInspectable NSString *cellIdentifier;
  8. @property (nonatomic, copy) CellConfigureBefore cellConfigureBefore;
  9. - (id)modelsAtIndexPath:(NSIndexPath *)indexPath;
  10. @end
  11. @implementation LMDataSource
  12. - (id)initWithIdentifier:(NSString *)identifier configureBlock:(CellConfigureBefore)before {
  13. if(self = [super init]) {
  14. _cellIdentifier = identifier;
  15. _cellConfigureBefore = [before copy];
  16. }
  17. return self;
  18. }
  19. - (id)modelsAtIndexPath:(NSIndexPath *)indexPath {
  20. return self.dataArray.count > indexPath.row ? self.dataArray[indexPath.row] : nil;
  21. }
  22. #pragma mark UITableViewDataSource
  23. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  24. {
  25. return !self.dataArray ? 0: self.dataArray.count;
  26. }
  27. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  28. {
  29. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath];
  30. id model = [self modelsAtIndexPath:indexPath];
  31. if(self.cellConfigureBefore) {
  32. self.cellConfigureBefore(cell, model,indexPath);
  33. }
  34. return cell;
  35. }
  36. #pragma mark UICollectionViewDataSource
  37. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
  38. {
  39. return !self.dataArray ? 0: self.dataArray.count;
  40. }
  41. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
  42. {
  43. UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:self.cellIdentifier forIndexPath:indexPath];
  44. id model = [self modelsAtIndexPath:indexPath];
  45. if(self.cellConfigureBefore) {
  46. self.cellConfigureBefore(cell, model,indexPath);
  47. }
  48. return cell;
  49. }
  50. - (NSMutableArray *)dataArray{
  51. if (!_dataArray) {
  52. _dataArray = [NSMutableArray arrayWithCapacity:5];
  53. }
  54. return _dataArray;
  55. }
  56. @end

自定义View封装繁重的UI代码

  1. #import <UIKit/UIKit.h>
  2. #import "MVCTableViewCell.h"
  3. @interface LGView : UIView
  4. @property (nonatomic, strong) UITableView *tableView;
  5. - (void)setDataSource:(id<UITableViewDataSource>)dataSource;
  6. @end
  7. @implementation LGView
  8. - (instancetype)initWithFrame:(CGRect)frame
  9. {
  10. self = [super initWithFrame:frame];
  11. if (self) {
  12. [self addSubview:self.tableView];
  13. }
  14. return self;
  15. }
  16. - (void)setDataSource:(id<UITableViewDataSource>)dataSource{
  17. self.tableView.dataSource = dataSource;
  18. }
  19. - (UITableView *)tableView{
  20. if (!_tableView) {
  21. _tableView = [[UITableView alloc] initWithFrame:self.frame style:UITableViewStylePlain];
  22. _tableView.tableFooterView = [UIView new];
  23. _tableView.backgroundColor = [UIColor whiteColor];
  24. [_tableView registerClass:[MVCTableViewCell class] forCellReuseIdentifier:[MVCTableViewCell reuserId]];
  25. }
  26. return _tableView;
  27. }
  28. @end

轻量级VC中的代码

  1. #import "MVCViewController.h"
  2. #import "LMDataSource.h"
  3. #import "MVCTableViewCell.h"
  4. #import "Present.h"
  5. #import "Model.h"
  6. #import "LGView.h"
  7. @interface MVCViewController ()
  8. @property (nonatomic, strong) LGView *lgView;
  9. @property (nonatomic, strong) LMDataSource *dataSource;
  10. @property (nonatomic, strong) Present *pt;
  11. @end
  12. @implementation MVCViewController
  13. - (void)viewDidLoad {
  14. [super viewDidLoad];
  15. // 建立关系
  16. self.view = self.lgView;
  17. [self.dataSource setDataArray:self.pt.dataArray];
  18. [self.lgView setDataSource:self.dataSource];
  19. }
  20. #pragma mark - lazy
  21. // UI布局代码
  22. - (LGView *)lgView{
  23. if(!_lgView){
  24. _lgView = [[LGView alloc] initWithFrame:self.view.bounds];
  25. }
  26. return _lgView;
  27. }
  28. // 数据提供层
  29. - (Present *)pt{
  30. if(!_pt){
  31. _pt = [[Present alloc] init];
  32. }
  33. return _pt;
  34. }
  35. // 数据代理层
  36. - (LMDataSource *)dataSource{
  37. if(!_dataSource){
  38. _dataSource = [[LMDataSource alloc] initWithIdentifier:[MVCTableViewCell reuserId] configureBlock:^(MVCTableViewCell *cell, Model *model, NSIndexPath *indexPath) {
  39. cell.model = model;
  40. }];
  41. }
  42. return _dataSource;
  43. }
  44. @end

3. MVP架构

上述案例中,还存在UIModel之间的强依赖关系

Cell中,UI的展示需要使用Model

  1. - (void)setModel:(Model *)model{
  2. _model = model;
  3. self.numLabel.text = model.num;
  4. self.nameLabel.text = model.name;
  5. }

当用户点击Cell上的某个按钮发生交互,同样需要修改Model

  1. - (void)didClickAddBtn:(UIButton *)sender{
  2. self.num++;
  3. }
  4. - (void)setNum:(int)num{
  5. _num = num;
  6. self.numLabel.text = [NSString stringWithFormat:@"%d",self.num];
  7. self.model.num = self.numLabel.text;
  8. }

对于UIModel层的解耦处理,可以采用MVP面向协议编程,即Model View Presenter(模型 视图 协调器)

3.1 特征

  • 任务均摊:将最主要的任务划分到PresenterModel,而View的功能较少

  • 可测试性:由于一个功能简单的View层,所以测试数业务逻辑变得简单

  • 易用性:比使用MVC模式的代码量更多,但MVP模式具备非常清晰的结构

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

3.2 设计模式

  • Model层不仅仅是创建一个数据对象,还应该包含网络请求,以及数据Cache的操作

  • View层就是一些封装、重用

  • Presenter层并不涉及数据的网络请求和Cache操作,只是搭建Model层和View层的一个桥梁

3.3 优势

  • 模型与视图完全分离,修改视图而不影响模型

  • 可以更高效地使用模型,因为所有交互都在Presenter

  • 把逻辑放在Presenter中,可以脱离用户接口进行单元测试

  • 可以将一个Presener用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁

3.4 UIModel解耦

创建LGProtocol,实现Cell中按钮点击的事件接口

  1. #import <Foundation/Foundation.h>
  2. @protocol LGProtocol <NSObject>
  3. - (void)didClickNum:(NSString *)num indexpath:(NSIndexPath *)indexpath;
  4. @end

Present中,实现LGProtocol协议的didClickNum方法

  1. #import <Foundation/Foundation.h>
  2. #import "Model.h"
  3. #import "LGProtocol.h"
  4. #import <YYKit.h>
  5. @interface Present : NSObject<LGProtocol>
  6. @property (nonatomic, strong) NSMutableArray *dataArray;
  7. @property (nonatomic, weak) id<LGProtocol> delegate;
  8. @end
  9. @implementation Present
  10. - (instancetype)init{
  11. if (self = [super init]) {
  12. [self loadData];
  13. }
  14. return self;
  15. }
  16. - (void)loadData{
  17. NSArray *temArray =
  18. @[
  19. @{@"name":@"HK",@"imageUrl":@"http://HK",@"num":@"99"},
  20. @{@"name":@"KD",@"imageUrl":@"http://KD",@"num":@"99"},
  21. @{@"name":@"CC",@"imageUrl":@"http://CC",@"num":@"99"},
  22. @{@"name":@"KC",@"imageUrl":@"http://KC",@"num":@"59"},
  23. @{@"name":@"LA",@"imageUrl":@"http://LA",@"num":@"24"}];
  24. for (int i = 0; i<temArray.count; i++) {
  25. Model *m = [Model modelWithDictionary:temArray[i]];
  26. [self.dataArray addObject:m];
  27. }
  28. }
  29. #pragma mark -LGProtocol
  30. - (void)didClickNum:(NSString *)num indexpath:(NSIndexPath *)indexpath{
  31. @synchronized (self) {
  32. if (indexpath.row < self.dataArray.count) {
  33. Model *model = self.dataArray[indexpath.row];
  34. model.num = num;
  35. }
  36. }
  37. }
  38. #pragma mark - lazy
  39. - (NSMutableArray *)dataArray{
  40. if (!_dataArray) {
  41. _dataArray = [NSMutableArray arrayWithCapacity:10];
  42. }
  43. return _dataArray;
  44. }
  45. @end

MVCTableViewCell中,删除UIModel的数据绑定。在按钮点击的交互方法中,调用delegatedidClickNum方法

  1. - (void)didClickAddBtn:(UIButton *)sender{
  2. self.num++;
  3. }
  4. - (void)setNum:(int)num{
  5. _num = num;
  6. self.numLabel.text = [NSString stringWithFormat:@"%d",self.num];
  7. // 发出响应 model delegate UI
  8. if (self.delegate && [self.delegate respondsToSelector:@selector(didClickNum:indexpath:)]) {
  9. [self.delegate didClickNum:self.numLabel.text indexpath:self.indexPath];
  10. }
  11. }
  12. // 删除UI对Model的数据绑定
  13. //- (void)setModel:(Model *)model{
  14. // _model = model;
  15. // self.numLabel.text = model.num;
  16. // self.nameLabel.text = model.name;
  17. //}

来到VC代码中LMDataSourceBlock,完成CellUIModel的数据绑定,以及delegateindexPath的设置

  1. #import "MVCViewController.h"
  2. #import "LMDataSource.h"
  3. #import "MVCTableViewCell.h"
  4. #import "Present.h"
  5. #import "Model.h"
  6. #import "LGView.h"
  7. @interface MVCViewController ()
  8. @property (nonatomic, strong) LGView *lgView;
  9. @property (nonatomic, strong) LMDataSource *dataSource;
  10. @property (nonatomic, strong) Present *pt;
  11. @end
  12. @implementation MVCViewController
  13. - (void)viewDidLoad {
  14. [super viewDidLoad];
  15. // 建立关系
  16. self.view = self.lgView;
  17. [self.dataSource setDataArray:self.pt.dataArray];
  18. [self.lgView setDataSource:self.dataSource];
  19. }
  20. #pragma mark - lazy
  21. // UI布局代码
  22. - (LGView *)lgView{
  23. if(!_lgView){
  24. _lgView = [[LGView alloc] initWithFrame:self.view.bounds];
  25. }
  26. return _lgView;
  27. }
  28. // 数据提供层
  29. - (Present *)pt{
  30. if(!_pt){
  31. _pt = [[Present alloc] init];
  32. }
  33. return _pt;
  34. }
  35. // 数据代理层
  36. - (LMDataSource *)dataSource{
  37. if(!_dataSource){
  38. __weak typeof(self) weakSelf = self;
  39. _dataSource = [[LMDataSource alloc] initWithIdentifier:[MVCTableViewCell reuserId] configureBlock:^(MVCTableViewCell *cell, Model *model, NSIndexPath *indexPath) {
  40. __strong __typeof(weakSelf)strongSelf = weakSelf;
  41. cell.numLabel.text = model.num;
  42. cell.nameLabel.text = model.name;
  43. cell.delegate = strongSelf.pt;
  44. cell.indexPath = indexPath;
  45. }];
  46. }
  47. return _dataSource;
  48. }
  49. @end

3.5 双向绑定

开发中遇到以下需求时,需要UI和Model进行双向绑定。例如:触发CelldidClickAddBtn事件,当num++大于5UI层只展示前两条数据

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

  1. #import <Foundation/Foundation.h>
  2. @protocol LGProtocol <NSObject>
  3. - (void)didClickNum:(NSString *)num indexpath:(NSIndexPath *)indexpath;
  4. - (void)reloadUI;
  5. @end

修改Present中的didClickNum方法,增加num大于5,只保留前两条数据,并调用delegatereloadUI方法刷新UI

  1. - (void)didClickNum:(NSString *)num indexpath:(NSIndexPath *)indexpath{
  2. @synchronized (self) {
  3. if (indexpath.row < self.dataArray.count) {
  4. Model *model = self.dataArray[indexpath.row];
  5. model.num = num;
  6. if ([num intValue] > 5) {
  7. [self.dataArray removeAllObjects];
  8. NSArray *temArray =
  9. @[
  10. @{@"name":@"HK",@"imageUrl":@"http://HK",@"num":@"99"},
  11. @{@"name":@"KC",@"imageUrl":@"http://KC",@"num":@"99"}];
  12. for (int i = 0; i<temArray.count; i++) {
  13. Model *m = [Model modelWithDictionary:temArray[i]];
  14. [self.dataArray addObject:m];
  15. }
  16. if (self.delegate && [self.delegate respondsToSelector:@selector(reloadUI)]) {
  17. [self.delegate reloadUI];
  18. }
  19. }
  20. }
  21. }
  22. }

修改LGView,实现LGProtocol协议的刷新UI方法

  1. #import <UIKit/UIKit.h>
  2. #import "MVCTableViewCell.h"
  3. #import "LGProtocol.h"
  4. @interface LGView : UIView<LGProtocol>
  5. @property (nonatomic, strong) UITableView *tableView;
  6. - (void)setDataSource:(id<UITableViewDataSource>)dataSource;
  7. @end
  8. @implementation LGView
  9. - (instancetype)initWithFrame:(CGRect)frame
  10. {
  11. self = [super initWithFrame:frame];
  12. if (self) {
  13. [self addSubview:self.tableView];
  14. }
  15. return self;
  16. }
  17. - (void)setDataSource:(id<UITableViewDataSource>)dataSource{
  18. self.tableView.dataSource = dataSource;
  19. }
  20. - (void)reloadUI{
  21. [self.tableView reloadData];
  22. }
  23. - (UITableView *)tableView{
  24. if (!_tableView) {
  25. _tableView = [[UITableView alloc] initWithFrame:self.frame style:UITableViewStylePlain];
  26. _tableView.tableFooterView = [UIView new];
  27. _tableView.backgroundColor = [UIColor whiteColor];
  28. [_tableView registerClass:[MVCTableViewCell class] forCellReuseIdentifier:[MVCTableViewCell reuserId]];
  29. }
  30. return _tableView;
  31. }
  32. @end

修改MVCViewController,将UIModel建立关系

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. // 建立关系
  4. self.view = self.lgView;
  5. [self.dataSource setDataArray:self.pt.dataArray];
  6. [self.lgView setDataSource:self.dataSource];
  7. //将UI和Model建立关系
  8. self.pt.delegate = self.lgView;
  9. }

3.6 案例总结

对于上述MVP的简单案例,其实还存在很多缺陷。这种简陋的代码,连开发中常见的需求都难以满足

例如以下一些情况:

  • 遇到更复杂的Cell形态

  • 使用过多的delegate等胶水代码

  • 当遇到模块层次更深的情况,相互之间难以通讯

所以,针对上述的问题,还要介绍一下MVP的真正使用方式

4. 适配器设计

当一个列表中出现多种Cell形态,可以使用适配器设计方式

创建KCBaseAdapter基类,基于UITableView的代理,实现基础方法

  1. #import <Foundation/Foundation.h>
  2. #import <UIKit/UIKit.h>
  3. NS_ASSUME_NONNULL_BEGIN
  4. @protocol KCBaseAdapterDelegate <NSObject>
  5. @optional
  6. - (void)didSelectCellData:(id)cellData;
  7. - (void)deleteCellData:(id)cellData;
  8. - (void)willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
  9. @end
  10. @protocol KCBaseAdapterScrollDelegate <NSObject>
  11. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView ;
  12. - (void)scrollViewDidScroll:(UIScrollView *)scrollView ;
  13. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView;
  14. @end
  15. @protocol KCBaseAdapterPullUpDelegate <NSObject>
  16. - (void)beginToRefresh;
  17. @end
  18. @interface KCBaseAdapter : NSObject<UITableViewDataSource, UITableViewDelegate>
  19. {
  20. }
  21. @property (nonatomic, weak) id<KCBaseAdapterDelegate> adapterDelegate;
  22. @property (nonatomic, weak) id<KCBaseAdapterScrollDelegate> adapterScrollDelegate;
  23. @property (nonatomic, weak) id<KCBaseAdapterPullUpDelegate> adapterPullUpDelegate;
  24. @property (nonatomic, strong) NSMutableArray *arr;
  25. - (float)getTableContentHeight;
  26. - (void)refreshCellByData:(id)data tableView:(UITableView*)tableView;
  27. - (NSArray*)getAdapterArray;
  28. - (void)setAdapterArray:(NSArray*)arr;
  29. @end
  30. @implementation KCBaseAdapter
  31. - (instancetype)init
  32. {
  33. self = [super init];
  34. if (self) {
  35. _arr = [NSMutableArray new];
  36. }
  37. return self;
  38. }
  39. - (NSArray*)getAdapterArray
  40. {
  41. return _arr;
  42. }
  43. - (void)setAdapterArray:(NSArray*)arr
  44. {
  45. [_arr removeAllObjects];
  46. [_arr addObjectsFromArray:arr];
  47. }
  48. - (float)getTableContentHeight
  49. {
  50. return 0;
  51. }
  52. #pragma mark - 抽象方法
  53. - (void)refreshCellByData:(id)data tableView:(UITableView*)tableView
  54. {
  55. }
  56. #pragma mark UITableView DataSource
  57. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  58. {
  59. return _arr.count;
  60. }
  61. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  62. {
  63. if (indexPath.row > self.getAdapterArray.count-1) {
  64. return [UITableViewCell new];
  65. }
  66. id cellData = [self.arr objectAtIndex:indexPath.row];
  67. UITableViewCell* cell = NULL;
  68. CCSuppressPerformSelectorLeakWarning(
  69. cell = [self performSelector:NSSelectorFromString([NSString stringWithFormat:@"tableView:cellFor%@:", [cellData class]]) withObject:tableView withObject:cellData];);
  70. return cell;
  71. }
  72. #pragma mark - UITableView Delegate
  73. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  74. {
  75. return 0;
  76. }
  77. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  78. {
  79. if (indexPath.row > self.getAdapterArray.count-1) {
  80. return;
  81. }
  82. [tableView deselectRowAtIndexPath:indexPath animated:false];
  83. id cellData = [self.arr objectAtIndex:indexPath.row];
  84. if (self.adapterDelegate) {
  85. if ([_adapterDelegate respondsToSelector:@selector(didSelectCellData:)]) {
  86. [_adapterDelegate didSelectCellData:cellData];
  87. }
  88. }
  89. }
  90. - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
  91. {
  92. if (indexPath.row > self.getAdapterArray.count-1) {
  93. return;
  94. }
  95. if (self.adapterPullUpDelegate && [self.adapterPullUpDelegate respondsToSelector:@selector(beginToRefresh)]) {
  96. //倒数第三个 发送请求
  97. if (tableView.style == UITableViewStyleGrouped) {
  98. if (self.getAdapterArray.count >1) {
  99. NSArray *dataArray = [self.getAdapterArray objectAtIndex:0];
  100. if (dataArray.count > 4 && dataArray.count - 4 == indexPath.row) {
  101. [self.adapterPullUpDelegate beginToRefresh];
  102. }
  103. }
  104. }
  105. else if (tableView.style == UITableViewStylePlain)
  106. {
  107. if (self.getAdapterArray.count > 4 && self.getAdapterArray.count - 4 == indexPath.row) {
  108. [self.adapterPullUpDelegate beginToRefresh];
  109. }
  110. }
  111. }
  112. if (self.adapterDelegate) {
  113. if ([_adapterDelegate respondsToSelector:@selector(willDisplayCell:forRowAtIndexPath:)]) {
  114. [_adapterDelegate willDisplayCell:cell forRowAtIndexPath:indexPath];
  115. }
  116. }
  117. }
  118. #pragma mark - UIScrollViewDelegate
  119. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
  120. if (self.adapterScrollDelegate && [self.adapterScrollDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) {
  121. [self.adapterScrollDelegate scrollViewWillBeginDragging:scrollView];
  122. }
  123. }
  124. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  125. if (self.adapterScrollDelegate && [self.adapterScrollDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
  126. [self.adapterScrollDelegate scrollViewDidScroll:scrollView];
  127. }
  128. }
  129. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
  130. if (self.adapterScrollDelegate && [self.adapterScrollDelegate respondsToSelector:@selector(scrollViewDidEndDragging:)]) {
  131. [self.adapterScrollDelegate scrollViewDidEndDragging:scrollView];
  132. }
  133. }
  134. @end

创建KCHomeAdapter,继承于KCBaseAdapter,实现针对首页的业务逻辑

  1. #import "KCHomeAdapter.h"
  2. #import "KCHomeTableViewCell.h"
  3. #import "KCChannelProfile.h"
  4. @implementation KCHomeAdapter
  5. - (CGFloat)getCellHeight:(NSInteger)row
  6. {
  7. CGFloat height = SCREEN_WIDTH*608/1080 + 54;
  8. KCChannelProfile *model = [self.getAdapterArray objectAtIndex:row];
  9. if (model.title.length > 0) {
  10. CGSize titleSize = [model.title sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14]}];
  11. if (titleSize.width > SCREEN_WIDTH - 35) {
  12. // 两行
  13. height +=67;
  14. }else{
  15. height +=50;
  16. }
  17. }else{
  18. height += 8;
  19. }
  20. return height;
  21. }
  22. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  23. return [self getCellHeight:indexPath.row];
  24. }
  25. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  26. return self.getAdapterArray.count;
  27. }
  28. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  29. KCChannelProfile* liveModel = self.getAdapterArray[indexPath.row];
  30. UITableViewCell *cell = nil;
  31. CCSuppressPerformSelectorLeakWarning (
  32. cell = [self performSelector:NSSelectorFromString([NSString stringWithFormat:@"tableView:cellForKCChannelProfile:"]) withObject:tableView withObject:liveModel];
  33. );
  34. return cell;
  35. }
  36. - (UITableViewCell *)tableView:(UITableView *)tableView cellForKCChannelProfile:(id)model {
  37. NSString *cellIdentifier = NSStringFromSelector(_cmd);
  38. KCHomeTableViewCell *cell = (KCHomeTableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
  39. if (!cell) {
  40. cell = [[KCHomeTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
  41. }
  42. KCChannelProfile* liveModel = model;
  43. [cell setCellContent:liveModel];
  44. return cell;
  45. }
  46. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  47. [tableView deselectRowAtIndexPath:indexPath animated:NO];
  48. id model = self.getAdapterArray[indexPath.row];
  49. if (self.adapterDelegate && [self.adapterDelegate respondsToSelector:@selector(didSelectCellData:)]) {
  50. [self.adapterDelegate didSelectCellData:model];
  51. }
  52. }
  53. @end

因为在项目中,针对不同业务的实现代码逻辑各不相同。我们在基类中只实现基础代码,通过继承或分类的形式,完成业务代码,以此方式做到去中心化,增强代码的复用性

5. context设计

创建宏定义,实现代理方法的万能调用

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

创建KCBaseViewController作为VC层的基类,内部实现UI层和Model层都依赖的Context上下文

  1. #import "KCBaseViewController.h"
  2. @interface KCBaseViewController ()
  3. @property (nonatomic, strong) NSMutableDictionary *eventMap;
  4. @property (nonatomic, assign) BOOL mvpEnabled;
  5. @end
  6. @implementation KCBaseViewController
  7. - (void)configMVP:(NSString*)name
  8. {
  9. self.mvpEnabled = true;
  10. self.rootContext = [[CDDContext alloc] init]; //strong
  11. self.context = self.rootContext; //weak
  12. //presentor
  13. Class presenterClass = NSClassFromString([NSString stringWithFormat:@"KC%@Presenter", name]);
  14. if (presenterClass != NULL) { // 缓存
  15. self.context.presenter = [presenterClass new];
  16. self.context.presenter.context = self.context;
  17. }
  18. //interactor
  19. Class interactorClass = NSClassFromString([NSString stringWithFormat:@"KC%@Interactor", name]);
  20. if (interactorClass != NULL) {
  21. self.context.interactor = [interactorClass new];
  22. self.context.interactor.context = self.context;
  23. }
  24. //view
  25. Class viewClass = NSClassFromString([NSString stringWithFormat:@"KC%@View", name]);
  26. if (viewClass != NULL) {
  27. self.context.view = [viewClass new];
  28. self.context.view.context = self.context;
  29. }
  30. //build relation
  31. self.context.presenter.view = self.context.view;
  32. self.context.presenter.baseController = self;
  33. self.context.interactor.baseController = self;
  34. self.context.view.presenter = self.context.presenter;
  35. self.context.view.interactor = self.context.interactor;
  36. }
  37. - (void)viewDidLoad
  38. {
  39. [super viewDidLoad];
  40. if (self.mvpEnabled) {
  41. self.context.view.frame = self.view.bounds;
  42. self.view = self.context.view;
  43. }
  44. KCLog(@"\n\nDid Load ViewController: %@\n\n", [self class]);
  45. }
  46. - (void)dealloc
  47. {
  48. KCLog(@"\n\nReleasing ViewController: %@\n\n", [self class]);
  49. [Notif removeObserver:self];
  50. }
  51. @end
  • VC下的contextrootContext
  • rootContext下包含presenterinteractorview的创建,并对各层级下的context进行赋值
  • 文件的命名,必须和代码规则一致

找到CDDContextCDDPresenterCDDInteractorCDDView的定义:

  1. #import <Foundation/Foundation.h>
  2. #import "NSObject+CDD.h"
  3. @class CDDContext;
  4. @class CDDView;
  5. @interface CDDPresenter : NSObject
  6. @property (nonatomic, weak) UIViewController* baseController;
  7. @property (nonatomic, weak) CDDView* view;
  8. @property (nonatomic, weak) id adapter; //for tableview adapter
  9. @end
  10. @interface CDDInteractor : NSObject
  11. @property (nonatomic, weak) UIViewController* baseController;
  12. @end
  13. @interface CDDView : UIView
  14. @property (nonatomic, weak) CDDPresenter* presenter;
  15. @property (nonatomic, weak) CDDInteractor* interactor;
  16. @end
  17. //Context bridges everything automatically, no need to pass it around manually
  18. @interface CDDContext : NSObject
  19. @property (nonatomic, strong) CDDPresenter* presenter;
  20. @property (nonatomic, strong) CDDInteractor* interactor;
  21. @property (nonatomic, strong) CDDView* view; //view holds strong reference back to context
  22. @end
  23. @implementation CDDPresenter
  24. @end
  25. @implementation CDDInteractor
  26. @end
  27. @implementation CDDView
  28. - (void)dealloc
  29. {
  30. self.context = nil;
  31. }
  32. @end
  33. @implementation CDDContext
  34. - (void)dealloc
  35. {
  36. NSLog(@"context being released");
  37. }
  38. @end
  • 对于Context上下文,它只负责信息的综合管理,不负责业务细节的实现,所以并不存在臃肿的现象

6. context分发

无论自定义View的层级有多深,都可以调用到context上下文

例如:直播页面中弹出的礼物列表弹窗,点击发送礼物的事件

  1. - (void)buttonSendClicked:(id)sender
  2. {
  3. if (_selectedItem) {
  4. KC(self.context.presenter, KCLiveStreamPresenterDeleagte, sendGiftWithIndex:(int)_selectedItem.tag);
  5. }
  6. }

View中的context上下文的构建方式,通过VC层对context进行设置,而context实际上是基于NSObject创建的CDD分类,在分类中使用关联对象的方式进行设置

  1. #import "NSObject+CDD.h"
  2. #import "CDDContext.h"
  3. #import <objc/runtime.h>
  4. @implementation NSObject (CDD)
  5. @dynamic context;
  6. - (void)setContext:(CDDContext*)object {
  7. objc_setAssociatedObject(self, @selector(context), object, OBJC_ASSOCIATION_ASSIGN);
  8. }
  9. - (CDDContext*)context {
  10. id curContext = objc_getAssociatedObject(self, @selector(context));
  11. if (curContext == nil && [self isKindOfClass:[UIView class]]) {
  12. //try get from superview, lazy get
  13. UIView* view = (UIView*)self;
  14. UIView* sprView = view.superview;
  15. while (sprView != nil) {
  16. if (sprView.context != nil) {
  17. curContext = sprView.context;
  18. break;
  19. }
  20. sprView = sprView.superview;
  21. }
  22. if (curContext != nil) {
  23. [self setContext:curContext];
  24. }
  25. }
  26. return curContext;
  27. }
  28. + (void)swizzleInstanceSelector:(SEL)oldSel withSelector:(SEL)newSel
  29. {
  30. Method oldMethod = class_getInstanceMethod(self, oldSel);
  31. Method newMethod = class_getInstanceMethod(self, newSel);
  32. if (!oldMethod || !newMethod)
  33. {
  34. return;
  35. }
  36. class_addMethod(self,
  37. oldSel,
  38. class_getMethodImplementation(self, oldSel),
  39. method_getTypeEncoding(oldMethod));
  40. class_addMethod(self,
  41. newSel,
  42. class_getMethodImplementation(self, newSel),
  43. method_getTypeEncoding(newMethod));
  44. method_exchangeImplementations(class_getInstanceMethod(self, oldSel),
  45. class_getInstanceMethod(self, newSel));
  46. }
  47. @end

如果当前对象为UIView的实例对象,并且context上下文不存在,会遍历当前对象的父容器,找到可用的context,并对当前对象的context进行赋值

这样就保证了在VC下的所有子View,都能找到可用上下文,从而解决模块之间的通讯问题

总结

MVC架构:

  • 设计模式

    • Model:模型层,封装了应用程序的数据,并定义操控和处理该数据的逻辑和运算

    • View:视图层,用于视图控件的渲染,并且对用户的操作作出响应

    • Controller:程序的视图对象和模型对象之间的交互和通讯,由控制器充当媒介,负责二者的执行设置和协调任务,并管理它们的生命周期

  • 常见问题

    • Controller中存在大量的代理、逻辑、UI和内部方法,导致VC层过重

    • ViewModel之间很容易产生依赖,导致耦合度过高

轻量化VC

  • VC的实际意义,只是建立模块之间的依赖关系

  • 避免在VC中加入繁重的UI代码、啰嗦的业务逻辑、很长的网络层代码、难受的代理功能

MVP架构:

  • 特征

    • 任务均摊:将最主要的任务划分到PresenterModel,而View的功能较少

    • 可测试性:由于一个功能简单的View层,所以测试数业务逻辑变得简单

    • 易用性:比使用MVC模式的代码量更多,但MVP模式具备非常清晰的结构

  • 设计模式

    • Model层不仅仅是创建一个数据对象,还应该包含网络请求,以及数据Cache的操作

    • View层就是一些封装、重用

    • Presenter层并不涉及数据的网络请求和Cache操作,只是搭建Model层和View层的一个桥梁

  • 优势

    • 模型与视图完全分离,修改视图而不影响模型

    • 可以更高效地使用模型,因为所有交互都在Presenter

    • 把逻辑放在Presenter中,可以脱离用户接口进行单元测试

    • 可以将一个Presener用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁