一种多种类Cell列表的实现

需求描述

有一个表格,需要显示不同种类的Cell,种类>10, 随时新增新的种类,而且各种类型有相似点,分多个系列,如何设计使可维护性比较高?这里以机票,火车票,酒店来举例。

架构选择

MVC MVVM VIPER
关于这几种架构不多说,实际采用的实现是基于MVVM,吸收VIPER的优点,组合成的MVVMIP架构,Model(Entity), Interactor, UIModel(VM), Presenter, View, 将MVVM中VM的职责进一步细分。


一种多种类Cell列表的实现_第1张图片
架构示意图

*Interactor 交互器 负责数据的获取Entity,生成UI用的Model
*Presenter 展示器 负责View的展示

实现

常规实现

先来谈一谈常见可能存在的一种实现方式:

  • UIModel

    机票、酒店、火车票,对应三种 UIModel,列表显示时还包含日期、按钮操作、信息提示等UI,这些信息将包含在三种 Model 中,通过参数来控制显示与否。

  • Cell

    对应3种 Model 有三种Cell,在创建 TableView 的地方需要注册不同Cell的 identifier,在Cell创建的地方,通过Switch Type 返回不同类型的Cell。

  • 事件回调

    每种 Cell 都有事件回调,那么就有3种Deleage, VC 需要实现这些协议。

好了,一切貌似比较顺利,现在要新加一种打车类型,需要做些什么?

step1 创建一个新的CellType枚举类型
step2 新增一种 Model,对应用车,大多数变量与前三者一致。
step3 创建一种新的Cell
step4 在创建 TableView 的地方需要注册新Cell的 identifier
step5 TableView 声明实现新的 delegate
step6 在Cell创建的地方,通过Switch Type 返回新的类型的Cell

在整个流程过程中需要重复做很多工作,会写很多类似的代码,也很难重用;
新的需求是不同渠道创建的机票、火车票将有另外一种显示方式,50%与原来一样,这个时候,相信就有点纠结了,如果新建新的类型,那么将有50%的代码和之前一样,如果追求代码的重用,扩充原来的类型,那么,不用多说,整个结构就越来越难以维护,无论是新增,还是修改,都很费劲。这样一来,加班就少不了了。

实现效果

那么,再来说说另外一种实现,最终实现的效果是,如果想新增一种cell,那么只需要三步:

step1

创建一个新的CellType枚举类型

step2

创建对应的UIModel,其type类型设置为第一步新建的type类型

step3

创建用于显示的UIView,对,没看错,是UIView,不是Cell,UIView的内容显示通过 SetUIModel 来控制。

实现细节

Model

对 Cell 类型进行更高层次的抽象:不仅仅机票、酒店、火车票,将日期、操作、说明也抽象成类型,定义BaseModel,通过继承的方式,分为数据类型 DataModel 和非数据类型 SpecialModel 两种,进行定义,通过多层继承可进一步避免重复定义变量。
将非数据类型也定义为类型的好处是,将这部分 UI 控制逻辑下沉到 Model 创建之处:

网络/持久化数据 Entity -> UIModel,在这个过程中,创建额外的非数据型UIModel,只要数据创建好,后期就不用再理相关逻辑了。

Cell

定义BaseCell, Cell子类型通过运行时动态创建,UI显示通过CardBaseView作为容器,加载到Cell 的 ContentView上。

BaseCell.m

- (void)configCellBy:(ScheduleModelBase*)model {
    self.model = model;
    CardBaseView* card = [self.contentView viewWithTag:tagScheduleView];
    card = [ScheduleCardViewMaker makeScheduleCardView:card byModel:model];

    if (card.tag != tagScheduleView) {
        
        self.backgroundColor = [UIColor clearColor];
        self.contentView.backgroundColor = [UIColor clearColor];
        card.tag = tagScheduleView;
        card.delegate = self;
        [self.contentView addSubview:card];
        
        [card mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(self.contentView);
            make.left.equalTo(self.contentView);
            make.bottom.equalTo(self.contentView).priorityLow();
            make.right.equalTo(self.contentView).priority(999);
        }];
    }
}

ScheduleCardViewMaker.m

+ (CardBaseView*)makeScheduleCardView:(CardBaseView*)card
                                      byModel:(ScheduleModelBase*)model {
    NSString* typeString = NSStringFromScheduleType(model.type);
        NSString* classString = [NSString stringWithFormat:@"Schedule%@CardView", [typeString substringFromIndex:12]];
        card = [self p_addCard:NSClassFromString(classString) onCard:card model:model];
    
    return card;
}
    
+ (CardBaseView*)p_addCard:(Class)class onCard:(CardBaseView*)card model:(ScheduleModelBase*)model  {
    if (!card) {
        card = [class new];
    }
    
    [card SetUIModel:model];
    
    return card;
}

通过一系列解耦,将变化分散到两端:Model 和 View,中间流程全部自动化。最上层View,减小粒度,方便组合重用。

View

手法

枚举与字符串的转化

通过一系列宏定义,实现枚举与字符串的互转

// 枚举定义展开 1-1
#define ENUM_VALUE(name, assign) name assign,
// 枚举转字符串case展开 2-1
#define ENUM_CASE(name, assign) case name: return @#name;

// 字符串转枚举展开 2-1
 #define ENUM_STRCMP(name, assign) if ([string isEqualToString:@#name]) return name;

// 枚举字符串互转函数展开 2
#define DEFINE_ENUM(EnumType, ENUM_DEF) \
NSString *NSStringFrom##EnumType(EnumType value) \
{ \
    switch(value) \
    { \
        ENUM_DEF(ENUM_CASE) \
        default: return @""; \
    } \
} \
EnumType EnumType##FromNSString(NSString *string) \
{ \
    ENUM_DEF(ENUM_STRCMP) \
    return (EnumType)0; \
}

// 枚举声明定义宏
#define DECLARE_ENUM(EnumType, ENUM_DEF) \
typedef NS_ENUM(NSInteger, EnumType) { \
    ENUM_DEF(ENUM_VALUE) \
}; \
NSString *NSStringFrom##EnumType(EnumType value); \
EnumType EnumType##FromNSString(NSString *string); \

/*example
 // step 1 .h 行程卡片类型枚举
 #define SCHEDULE_TYPE(__x) \
 __x(ScheduleTypeFlight, ) \
 __x(ScheduleTypeSpecial, ) \
 __x(ScheduleTypeSpecialTime, ) \
 __x(ScheduleTypeCount, ) \
 
 // step 2 .h 声明
 DECLARE_ENUM(ScheduleType, SCHEDULE_TYPE)
 
 // step 3 .m
 DEFINE_ENUM(ScheduleType, SCHEDULE_TYPE)
 
 // 自动生成函数 枚举转字符串
 //NSString *NSStringFromScheduleType(ScheduleType value);
 // 自动生成函数 字符串转枚举
 //EnumType ScheduleTypeFromNSString(NSString *string);
 */

Cell子类自动创建

#define RegTableCellClass(cellName) \
Class clazz##cellName = objc_allocateClassPair(self, cellName.UTF8String, 0); \
objc_registerClassPair(clazz##cellName);

#define ENUM_TO_CSTR_CASE(enumType) \
[NSString stringWithCString:#enumType encoding:NSASCIIStringEncoding]

BaseCell.m

static NSMutableArray* subCell = nil;

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        subCell = [NSMutableArray new];
        for (int type = 0; type < ScheduleTypeCount; ++type) {
            NSString* cellString = [NSString stringWithFormat:@"%@Cell", NSStringFromScheduleType(type)];
            [subCell addObject:cellString];
        }
        
        [subCell enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            RegTableCellClass(obj);
        }];
    });
}

// 运行时注册子cell重用标识符
+ (void)regSubClassOn:(UITableView*)tableView {
    [subCell enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [tableView registerClass:NSClassFromString(obj) forCellReuseIdentifier:obj];
    }];
}

View Delegate到Cell的转发

BaseCell.m

因为是View放置在Cell的ContentView上,因此,View的Delegate是Cell,Cell通过消息转发实现回调,避免Cell实现中手写回调中转。

-(void)forwardInvocation:(NSInvocation *)anInvocation {
    [anInvocation invokeWithTarget:self.delegate];
}

你可能感兴趣的:(一种多种类Cell列表的实现)