一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情。
适配器设计
当一个列表中出现多种Cell
形态,可以使用适配器设计方式
创建KCBaseAdapter
基类,基于UITableView
的代理,实现基础方法
#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;
@property (nonatomic, strong) NSMutableArray *arr;
- (float)getTableContentHeight;
- (void)refreshCellByData:(id)data tableView:(UITableView*)tableView;
- (NSArray*)getAdapterArray;
- (void)setAdapterArray:(NSArray*)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
复制代码
创建KCHomeAdapter
,继承于KCBaseAdapter
,实现针对首页的业务逻辑
#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
复制代码
因为在项目中,针对不同业务的实现代码逻辑各不相同。我们在基类中只实现基础代码,通过继承或分类的形式,完成业务代码,以此方式做到去中心化,增强代码的复用性
context设计
创建宏定义,实现代理方法的万能调用
#define KC(instance, protocol, message) [(id<protocol>)(instance) message]
复制代码
创建KCBaseViewController
作为VC
层的基类,内部实现UI
层和Model
层都依赖的Context
上下文
#import "KCBaseViewController.h"
@interface KCBaseViewController ()
@property (nonatomic, strong) NSMutableDictionary *eventMap;
@property (nonatomic, assign) BOOL mvpEnabled;
@end
@implementation KCBaseViewController
- (void)configMVP:(NSString*)name
{
self.mvpEnabled = true;
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;
}
//interactor
Class interactorClass = NSClassFromString([NSString stringWithFormat:@"KC%@Interactor", name]);
if (interactorClass != NULL) {
self.context.interactor = [interactorClass new];
self.context.interactor.context = self.context;
}
//view
Class viewClass = NSClassFromString([NSString stringWithFormat:@"KC%@View", name]);
if (viewClass != NULL) {
self.context.view = [viewClass new];
self.context.view.context = self.context;
}
//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
复制代码
VC
下的context
为rootContext
rootContext
下包含presenter
、interactor
、view
的创建,并对各层级下的context
进行赋值- 文件的命名,必须和代码规则一致
找到CDDContext
、CDDPresenter
、CDDInteractor
、CDDView
的定义
#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
@implementation CDDPresenter
@end
@implementation CDDInteractor
@end
@implementation CDDView
- (void)dealloc
{
self.context = nil;
}
@end
@implementation CDDContext
- (void)dealloc
{
NSLog(@"context being released");
}
@end
复制代码
Context
上下文,它只负责信息的综合管理,不负责业务细节的实现,所以并不存在臃肿的现象
context分发
无论自定义View
的层级有多深,都可以调用到context
上下文
例如:直播页面中弹出的礼物列表弹窗,点击发送礼物的事件
- (void)buttonSendClicked:(id)sender
{
if (_selectedItem) {
KC(self.context.presenter, KCLiveStreamPresenterDeleagte, sendGiftWithIndex:(int)_selectedItem.tag);
}
}
复制代码
子View
中的context
上下文的构建方式,通过VC
层对context
进行设置,而context
实际上是基于NSObject
创建的CDD
分类,在分类中使用关联对象的方式进行设置
#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;
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
复制代码
如果当前对象为UIView
的实例对象,并且context
上下文不存在,会遍历当前对象的父容器,找到可用的context
,并对当前对象的context
进行赋值
这样就保证了在VC下的所有子View,都能找到可用上下文,从而解决模块之间的通讯问题