阅读 368

用runtime优化tableView写法

从一张图开始:

按模块数据绑定显示样式

像这种界面,布局会比较复杂,每一section的头尾显示及点击事件都是动态的,而且里面的内容是几个row也不确定,这个界面逻辑整理后是:

NoDataCell-相关商城-相关医生-相关医院-相关问答-相关资讯-微脉好物-热门医生-热门医院-热门问答-热门资讯
复制代码

然后就是设置11个section,每个section里有三个cell,通用的titleSectionCell和moreSectionCell,还有每个section对应的自己的内容cell,每个cell负责自己的UI显示及点击事件。

整理后 逻辑思路是清晰了,但是怎么写代码呢? 如果按照思路说的这样每个section都得判断去写对应的三个cell还包括个字赋值及点击事件,想一想cellForRow里代码,额。。得写多大一坨啊?

是不是可以更有效更优雅的写这段代码呢? 想一下这段里的共性,通用的头尾好处理,内容cell的个数由各自的数组控制 cell的选择和index有关,而且整个tableView布局是固定的.

所以我们可以这样:

//排版方式:相关商品>医生>医院>问答>疾病标签>资讯>热门商品>医生>医院>问答>资讯
    NSDictionary *mallSectionDic     = @{kCellTitle:@"相关商品", kCellHasMore:@(self.mallHasMore), kCellHasResult:@(self.mallHasResult), kCellClass:@"WMSearchResultMallCell", kCellTitleMoreSelector:@"relativeMallMoreClick", kCellDataArray:self.resultMallArrray, kCellMoreText:@"查看更多商品"};
    NSDictionary *doctorSectionDic   = @{kCellTitle:@"相关医生", kCellHasMore:@(self.doctorHasMore), kCellHasResult:@(self.doctorHasResult), kCellClass:@"WMSearchResultDoctorCell", kCellTitleMoreSelector:@"relativeDoctorMoreClick", kCellDataArray:self.resultDoctorArrray, kCellMoreText:@"查看更多医生"};
    NSDictionary *hospitalSectionDic = @{kCellTitle:@"相关医院", kCellHasMore:@(self.hospitalHasMore), kCellHasResult:@(self.hospitalHasResult), kCellClass:@"WMSearchResultHospitalCell", kCellTitleMoreSelector:@"relativeHospitalMoreClick", kCellDataArray:self.resultHospitalArrray, kCellMoreText:@"查看更多医院"};
    NSDictionary *qaSectionDic       = @{kCellTitle:@"相关问答", kCellHasMore:@(self.QAHasMore), kCellHasResult:@(self.QAHasResult), kCellClass:@"WMSearchResultQACell", kCellTitleMoreSelector:@"relativeQAMoreClick", kCellDataArray:self.resultQAArrray,kCellMoreText:@"查看更多问答"};
//    NSDictionary *sicknessSectionDic  = @{kCellTitle:@"疾病", kCellHasMore:@(self.sicknessHasMore), kCellHasResult:@(self.sicknessHasResult), kCellClass:@"WMSearchResultQACell", kCellTitleMoreSelector:@"relativeSicknessMoreClick", kCellDataArray:self.resultSicknessArray,kCellMoreText:@"查看更多疾病"};
    NSDictionary *newsSectionDic     = @{kCellTitle:@"相关资讯", kCellHasMore:@(self.newsHasMore), kCellHasResult:@(self.newsHasResult), kCellClass:@"WMSearchResultNewsCell", kCellTitleMoreSelector:@"relativeNewsMoreClick", kCellDataArray:self.resultNewsArrray, kCellMoreText:@"查看更多资讯"};
    NSDictionary *recommentMallSectionDic        = @{kCellTitle:@"热门商品", kCellHasMore:@(self.mallHasMore), kCellHasResult:@(self.mallHasResult), kCellClass:@"WMSearchResultMallCell", kCellTitleMoreSelector:@"relativeMallMoreClick", kCellDataArray:self.resultMallArrray, kCellMoreText:@"查看更多商品"};
    NSDictionary *recommentDoctorSectionDic      = @{kCellTitle:@"周边热门医生", kCellHasMore:@(self.doctorHasMore), kCellHasResult:@(self.doctorHasResult), kCellClass:@"WMSearchResultDoctorCell", kCellTitleMoreSelector:@"recomendDoctorMoreClick", kCellDataArray:self.resultDoctorArrray, kCellMoreText:@"查看更多医生"};
    NSDictionary *recommentHospitalSectionDic    = @{kCellTitle:@"周边热门医院", kCellHasMore:@(self.hospitalHasMore), kCellHasResult:@(self.hospitalHasResult), kCellClass:@"WMSearchResultHospitalCell", kCellTitleMoreSelector:@"recomendHospitalMoreClick", kCellDataArray:self.resultHospitalArrray, kCellMoreText:@"查看更多医院"};
    NSDictionary *recommentQaSectionDic          = @{kCellTitle:@"热门问答", kCellHasMore:@(self.QAHasMore), kCellHasResult:@(self.QAHasResult), kCellClass:@"WMSearchResultQACell", kCellTitleMoreSelector:@"recomendQAMoreClick", kCellDataArray:self.resultQAArrray, kCellMoreText:@"查看更多问答"};
    NSDictionary *recommentNewsSectionDic        = @{kCellTitle:@"热门资讯", kCellHasMore:@(self.newsHasMore), kCellHasResult:@(self.newsHasResult), kCellClass:@"WMSearchResultNewsCell", kCellTitleMoreSelector:@"recomendNewsMoreClick", kCellDataArray:self.resultNewsArrray, kCellMoreText:@"查看更多资讯"};
    self.impSearchResultDataArray = @[mallSectionDic, doctorSectionDic, hospitalSectionDic, qaSectionDic, newsSectionDic, recommentMallSectionDic, recommentDoctorSectionDic, recommentHospitalSectionDic, recommentQaSectionDic, recommentNewsSectionDic];
复制代码
  • 用一个数组控制,里面对应各自section的字典,字典里面各自负责自己的titleSection和moreSection的显示及点击事件/结果bool/各自的自定义cell/各自的数据源。

  • 因为字典里的数据源NSMutableArray是浅拷贝 由指针指着地址,所以后面在数据请求后能正常一直使用。

  • 字典里的cell,通过字符串转class的方法,获取到对应的cell,这些cell都作统一的数组赋值 再在各自的赋值方法里用各自的model取值,用共性来解耦。

  • 字典里的点击事件,用imp来实现。通过Runtime的消息传递机制,直接执行imp指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息还要高效一些。

代码如下:

/** 搜索结果 -- cellForRow **/
- (UITableViewCell *)searchResultTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // 11 section --- nodata,热门商城,医生,医院,问答,资讯,相关附近商城,医生,附近医院,问答,资讯
    if (indexPath.section == 0) {
        // noData - Cell
        kWeakSelf
        WMSearchResultNoDataCell *cell = [tableView dequeueReusableCellWithIdentifier:[WMSearchResultNoDataCell reuseIdentifier] forIndexPath:indexPath];
        [cell configSearchText:self.searchKeyWords searchNoDataType:SearchNoDataTypeTabSummary];
        cell.moreBtnClickBlock = ^{
            [weakSelf skipToAskViewController];
        };
        return cell;
    }else {
        NSDictionary *impDictionary = self.impSearchResultDataArray[indexPath.section-1];
        if (indexPath.row == 0) {
            /*** sectionTitle - cell ***/
            WMSearchResultSectionTitleCell *cell = [tableView dequeueReusableCellWithIdentifier:[WMSearchResultSectionTitleCell reuseIdentifier] forIndexPath:indexPath];
            [cell configSectionTitle:impDictionary[kCellTitle] hasMore:impDictionary[kCellHasMore]];
            // imp方法转换
            [cell performSelector:@selector(setSectionTitleMoreBtnClickedBlock:) withObject:^(NSIndexPath *indexPath) {
                SEL selector = NSSelectorFromString(impDictionary[kCellTitleMoreSelector]);
                if ([self respondsToSelector:selector])]) {
                   IMP imp = [self methodForSelector:selector])];
                   void (*func)(id, SEL) = (void *)imp;
                   func(self, selector));
                }
            }];
            return cell;
        }else {
            NSArray *dataArray = impDictionary[kCellDataArray];
            BOOL hasResult     = impDictionary[kCellHasResult];
            BOOL hasMore       = impDictionary[kCellHasMore];
            NSString *moreText = impDictionary[kCellMoreText];
            if (hasMore && (indexPath.row == (dataArray.count+1))) {
                /*** sectionMore - cell ***/
                WMSearchResultSectionMoreCell *cell = [tableView dequeueReusableCellWithIdentifier:[WMSearchResultSectionMoreCell reuseIdentifier] forIndexPath:indexPath];
                [cell configMoreText:moreText];
                return cell;
            }else {
               /*** 各自内容cell  *****/
                // 获取cell类名的重用标识符
                NSString *cellIndentifer = impDictionary[kCellClass];
                // 通过重用标识字符串创建类
                Class cellClass = NSClassFromString(cellIndentifer);
                UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIndentifer];
                if (!cell) {
                    cell = [[cellClass alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndentifer];
                }
                if (dataArray.count > (indexPath.row-1)) {
                    if ([cell respondsToSelector:@selector(configDataArray:indexItem:)]) {
                      // 执行赋值方法
                        [cell performSelector:@selector(configDataArray:indexItem:) withObject:dataArray withObject:[NSNumber numberWithInteger:(indexPath.row-1)]];
                    }
                }
                return cell;
            }
        }
    }
}
复制代码

结合runtime优化tableView

performSelector是在iOS中的一种方法调用方式,是运行时系统负责去找方法的,在编译时候不做任何校验。
他可以向一个对象传递任何消息,而不需要在编译的时候声明这些方法。所以这也是runtime的一种应用方式。
所以performSelector和直接调用方法的区别就在与runtime。直接调用编译是会自动校验。如果方法不存在,那么直接调用在编译时候就能够发现,编译器会直接报错。
但是使用performSelector的话一定是在运行时候才能发现,如果此方法不存在就会崩溃。所以如果使用performSelector时,为了程序的健壮性,会使用检查方法respondsToSelector。

通过Runtime的消息传递机制,直接执行imp指向的函数实现:

SEL selector = NSSelectorFromString(impDictionary[kCellTitleMoreSelector]);

IMP imp = [self methodForSelector:selector];

void (*func)(id, SEL) = (void *)imp;

func(self, selector);
                   
复制代码

SEL : 类成员方法的指针,其实只是方法编号。
IMP:一个函数指针,保存了方法的地址。IMP是”implementation”的缩写,它是objetive-C 方法(method)实现代码块的地址。

IMP和SEL关系:
每一个继承于NSObject的类都能自动获得runtime的支持。
在这样的一个类中,有一个isa指针,指向该类定义的数据结构体,这个结构体是由编译器编译时为类(需继承于NSObject)创建的。
在这个结构体中有包括了指向其父类类定义的指针以及 Dispatch table。
Dispatch table是一张SEL和IMP的对应表。
也就是说方法编号SEL最后还是要通过Dispatch table表寻找到对应的IMP,IMP就是一个函数指针,然后执行这个方法。

实现步骤:

  1. 通过方法获得SEL 方法编号:
SEL methodId=@selector(methodName);或者SEL methodId = NSSelectorFromString(methodName); 
复制代码
  1. 通过方法编号获得IMP:
IMP imp = [self methodForSelector:methodId];  
复制代码
  1. 执行IMP:
void (*func)(id, SEL, id) = (void *)imp;   
func(self, methodName,param);
复制代码

然后直接在类里写methodName对应的点击事件方法就可以了,不用在cellForRow里通过代理或block写事件或在didSeclectRow里再根据index来判断了。

到这cellForRow里面的内容就差不多写完了,再说说tableViewCell高度计算

简单说说tableView自定义Cell的写法,个人不建议用Xib,建议手代码用Masonry布局。

masonry主要用三种写法,make/remake/update,根据具体样式的变化程度决定用哪个,一般make就足够了。

代码规范: 导入头文件、命名、UILoad、DataLoad、PrivateAction、Delegate、LazyLoad。。

关注下面的标签,发现更多相似文章
评论