逆向微信-分析學習微信是如何快速構建靜態TableView介面的

dev_liyang發表於2018-06-07

example_home.png

背景:

在搭建APP靜態TableView介面時,都是每個vc對應建立一個UITableView,然後實現UITableViewDataSource、UITableViewDelegate等方法,這樣的開發方式有幾大弊端:
1)效率不高,每個介面都得建立,實現協議。
2)cell的點選事件區分時需要一大堆的if/else。
3)介面元素變化時,維護起來非常蛋疼,需要修改好幾個地方的if/else。
在分析完微信後,發現微信搭建靜態TableView頁面時,並不會出現上面幾個問題,搭建非常easy,所以決定將學習到的思路分享出來大家一起交流學習。

分析過程中用到的工具:

1.一臺越獄的5s
2. dumpdecrypted(砸殼)
3.class-dump(匯出標頭檔案)
4.IDA(反彙編)
5.cycript(除錯)

逆向的基礎知識就不概述了,本文章主要是對微信進行靜態分析

一、找到關鍵類

1.MMTableViewInfo

通過觀察多個靜態頁面的vc,發現vc裡沒有直接建立UITableView,而是通過MMTableViewInfo這個類建立的,MMTableViewInfo裡有個_tableView成員變數,並實現了UITableViewDataSource、UITableViewDelegate,所以無誤。下圖是MMTableViewInfo標頭檔案截圖部分內容:

MMTableViewInfo.png

2.MMTableViewSectionInfo

通過觀察,很容易看出MMTableViewInfo中的成員變數_arrSections是_tableView的資料來源,除錯列印其元素是MMTableViewSectionInfo物件。下圖是MMTableViewSectionInfo標頭檔案截圖部分內容:

MMTableViewSectionInfo.png

3.MMTableViewCellInfo

通過觀察,猜測MMTableViewSectionInfo中的_arrCells應該是每個section中的cell陣列,除錯列印其元素是MMTableViewCellInfo物件。下圖是MMTableViewCellInfo標頭檔案截圖部分內容:

MMTableViewCellInfo.png

二、通過反向推理,正向梳理邏輯

1.觀察MMTableViewCellInfo標頭檔案,通過fCellHeight、cellStyle、accessoryType、+ (id)normalCellForTitle:(id)arg1 rightValue:(id)arg2這幾個屬性和方法,應該可以想到,這個類就是為cell準備資料的。

2.觀察MMTableViewSectionInfo標頭檔案,- (void)addCell:(id)arg1;通過該方法新增cellInfo到_arrCells裡構成了一個組的資料

3.觀察MMTableViewInfo標頭檔案,- (void)addSection:(id)arg1,可以想到是新增sectionInfo到_arrSections裡構成了UITableView的資料來源

現在知道了三者的構成關係,接下來的重點就是去分析其內部是如何實現的了。

三、分析內部實現

接下來通過IDA反彙編工具,檢視每個類具體實現的虛擬碼

1. MMTableViewCellInfo的實現

先看下虛擬碼(因封裝的方法較多,這裡只分析一個方法):

MMTableViewCellInfo normalcell.png
分析轉化為oc程式碼是這樣的,類名字首我使用了LY,注意:demo裡對基礎的cellInfo做了一層封裝。
LYTableViewCellInfo.png
第二個方法有SEL和target,這裡是微信對cell的選中事件進行了處理,使用了target/action方式,所以監聽cell的點選時不需要使用代理,使得每個cell有自己的action,即做到了解耦,又不用寫一堆的if/else了。
微信對一些資訊使用kvc進行存取,比如這裡的title(textLabel)和rightValue(detailTextLabel),存取的方法在MMTableViewCellInfo的父類MMTableViewUserInfo裡。

2. MMTableViewSectionInfo的實現

這個類的實現相對簡單,現在只看cell是如何新增的。

///新增cell
- (void)addCell:(LYTableViewCellInfo *)cell{
    [_arrCells addObject:cell];
}
複製程式碼

3. MMTableViewInfo的實現

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return _arrSections.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    LYTableViewSectionInfo *sectionInfo = _arrSections[section];
    return [sectionInfo getCellCount];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    LYTableViewSectionInfo *sectionInfo = _arrSections[indexPath.section];
    LYTableViewCellInfo *cellInfo = [sectionInfo getCellAt:indexPath.row];
    return cellInfo.fCellHeight;
}
複製程式碼
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    LYTableViewSectionInfo *sectionInfo = _arrSections[indexPath.section];
    LYTableViewCellInfo *cellInfo = [sectionInfo getCellAt:indexPath.row];
    
    NSString *iden = [NSString stringWithFormat:@"LYTableViewInfo_%zd_%zd", indexPath.section, indexPath.row];
    LYTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:iden];
    if (!cell) {
        cell = [[LYTableViewCell alloc] initWithStyle:cellInfo.cellStyle reuseIdentifier:iden];
    }
    
    cell.accessoryType = cellInfo.accessoryType;
    cell.selectionStyle = cellInfo.selectionStyle;
    cell.textLabel.text = [cellInfo getUserInfoValueForKey:@"title"];//通過LYTableViewCellInfo 父類方法kvc獲取到
    cell.detailTextLabel.text = [cellInfo getUserInfoValueForKey:@"rightValue"];//通過LYTableViewCellInfo 父類方法kvc獲取到
    
    return cell;
}
複製程式碼
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    LYTableViewSectionInfo *sectionInfo = _arrSections[indexPath.section];
    LYTableViewCellInfo *cellInfo = [sectionInfo getCellAt:indexPath.row];
    
    id target = cellInfo.actionTarget;
    SEL selector = cellInfo.actionSel;
    
    if (cellInfo.selectionStyle) {
        if ([target respondsToSelector:selector]) {
            [target performSelector:selector withObject:cellInfo withObject:indexPath];//建立cellInfo時,target傳遞並實現了SEL事件,這裡就會傳送這個訊息,從而實現cell的點選事件
        }
    }
}
複製程式碼

該類裡的資料來源就是MMTableViewSectionInfo和MMTableViewCellInfo,前面構建好了這兩,這裡直接就能用了。 看下最簡單的呼叫示例:

#pragma mark - Creat View
- (void)creatTableView{
    _tableViewInfo = [[LYTableViewInfo alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    [self.view addSubview:[_tableViewInfo getTableView]];
    
    //cell資料
    LYTableViewCellInfo *noactionCell = [LYTableViewCellInfo normalCellForTitle:@"無點選事件" rightValue:@"沒有"];
    LYTableViewCellInfo *actionCell = [LYTableViewCellInfo normalCellForSel:@selector(actionCellClick) target:self title:@"有點選事件" rightValue:@"" accessoryType:UITableViewCellAccessoryDisclosureIndicator];
    
    //section資料
    LYTableViewSectionInfo *sectionInfo = [LYTableViewSectionInfo sectionInfoDefaut];

    //新增
    [sectionInfo addCell:noactionCell];
    [sectionInfo addCell:actionCell];
    [_tableViewInfo addSection:sectionInfo];
    
    //重新整理
    [[_tableViewInfo getTableView] reloadData];
}

#pragma mark - Event
- (void)actionCellClick{
    NSLog(@"點選了actionCell");
}
複製程式碼

通過上面一段程式碼實現如下:

baseExample.png

總結:

以最簡單最基礎的案例介紹了微信的構建方式,此方式構建滿足了元件的可複用性、可維護性、高效性。 這裡只是做最簡單介紹,大家可根據自己的業務需求對相應的方法做調整,做擴充套件。

倉庫裡兩個Demo,一個是最基礎的元件,一個是稍微完善的元件 github地址

相關文章