iOS之MVP架构模式 二

一起养成写作习惯!这是我参与「掘金日新计划 · 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下的contextrootContext
  • rootContext下包含presenterinteractorview的创建,并对各层级下的context进行赋值
  • 文件的命名,必须和代码规则一致

找到CDDContextCDDPresenterCDDInteractorCDDView的定义

#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,都能找到可用上下文,从而解决模块之间的通讯问题

猜你喜欢

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