用runtime優化tableView寫法

從一張圖開始:

按模塊數據綁定顯示樣式

像這種界面,佈局會比較複雜,每一section的頭尾顯示及點擊事件都是動態的,並且裏面的內容是幾個row也不肯定,這個界面邏輯整理後是:數組

NoDataCell-相關商城-相關醫生-相關醫院-相關問答-相關資訊-微脈好物-熱門醫生-熱門醫院-熱門問答-熱門資訊
複製代碼

而後就是設置11個section,每一個section裏有三個cell,通用的titleSectionCell和moreSectionCell,還有每一個section對應的本身的內容cell,每一個cell負責本身的UI顯示及點擊事件。bash

整理後 邏輯思路是清晰了,可是怎麼寫代碼呢? 若是按照思路說的這樣每一個section都得判斷去寫對應的三個cell還包括個字賦值及點擊事件,想想cellForRow裏代碼,額。。得寫多大一坨啊?markdown

是否是能夠更有效更優雅的寫這段代碼呢? 想一下這段裏的共性,通用的頭尾好處理,內容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取值,用共性來解耦。ui

  • 字典裏的點擊事件,用imp來實現。經過Runtime的消息傳遞機制,直接執行imp指向的函數實現,這樣省去了Runtime消息傳遞過程當中所作的一系列查找操做,會比直接向對象發送消息還要高效一些。spa

代碼以下:代理

/** 搜索結果 -- 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。。

相關文章
相關標籤/搜索