逆向微信-分析學(xué)習(xí)微信是如何快速構(gòu)建靜態(tài)TableView界面的

example_home.png
背景:

在搭建APP靜態(tài)TableView界面時(shí),都是每個(gè)vc對(duì)應(yīng)創(chuàng)建一個(gè)UITableView,然后實(shí)現(xiàn)UITableViewDataSource、UITableViewDelegate等方法,這樣的開(kāi)發(fā)方式有幾大弊端:
1)效率不高,每個(gè)界面都得創(chuàng)建,實(shí)現(xiàn)協(xié)議。
2)cell的點(diǎn)擊事件區(qū)分時(shí)需要一大堆的if/else
3)界面元素變化時(shí),維護(hù)起來(lái)非常蛋疼,需要修改好幾個(gè)地方的if/else
在分析完微信后,發(fā)現(xiàn)微信搭建靜態(tài)TableView頁(yè)面時(shí),并不會(huì)出現(xiàn)上面幾個(gè)問(wèn)題,搭建非常easy,所以決定將學(xué)習(xí)到的思路分享出來(lái)大家一起交流學(xué)習(xí)。

分析過(guò)程中用到的工具:

1.一臺(tái)越獄的5s
2. dumpdecrypted(砸殼)
3.class-dump(導(dǎo)出頭文件)
4.IDA(反匯編)
5.cycript(調(diào)試)

逆向的基礎(chǔ)知識(shí)就不概述了,本文章主要是對(duì)微信進(jìn)行靜態(tài)分析

一、找到關(guān)鍵類(lèi)

1.MMTableViewInfo

通過(guò)觀察多個(gè)靜態(tài)頁(yè)面的vc,發(fā)現(xiàn)vc里沒(méi)有直接創(chuàng)建UITableView,而是通過(guò)MMTableViewInfo這個(gè)類(lèi)創(chuàng)建的,MMTableViewInfo里有個(gè)_tableView成員變量,并實(shí)現(xiàn)了UITableViewDataSource、UITableViewDelegate,所以無(wú)誤。下圖是MMTableViewInfo頭文件截圖部分內(nèi)容:
MMTableViewInfo.png
2.MMTableViewSectionInfo

通過(guò)觀察,很容易看出MMTableViewInfo中的成員變量_arrSections是_tableView的數(shù)據(jù)源,調(diào)試打印其元素是MMTableViewSectionInfo對(duì)象。下圖是MMTableViewSectionInfo頭文件截圖部分內(nèi)容:
MMTableViewSectionInfo.png
3.MMTableViewCellInfo

通過(guò)觀察,猜測(cè)MMTableViewSectionInfo中的_arrCells應(yīng)該是每個(gè)section中的cell數(shù)組,調(diào)試打印其元素是MMTableViewCellInfo對(duì)象。下圖是MMTableViewCellInfo頭文件截圖部分內(nèi)容:
MMTableViewCellInfo.png

二、通過(guò)反向推理,正向梳理邏輯

1.觀察MMTableViewCellInfo頭文件,通過(guò)fCellHeight、cellStyle、accessoryType、+ (id)normalCellForTitle:(id)arg1 rightValue:(id)arg2這幾個(gè)屬性和方法,應(yīng)該可以想到,這個(gè)類(lèi)就是為cell準(zhǔn)備數(shù)據(jù)的。

2.觀察MMTableViewSectionInfo頭文件,- (void)addCell:(id)arg1;通過(guò)該方法添加cellInfo到_arrCells里構(gòu)成了一個(gè)組的數(shù)據(jù)

3.觀察MMTableViewInfo頭文件,- (void)addSection:(id)arg1,可以想到是添加sectionInfo到_arrSections里構(gòu)成了UITableView的數(shù)據(jù)源

現(xiàn)在知道了三者的構(gòu)成關(guān)系,接下來(lái)的重點(diǎn)就是去分析其內(nèi)部是如何實(shí)現(xiàn)的了。

三、分析內(nèi)部實(shí)現(xiàn)

接下來(lái)通過(guò)IDA反匯編工具,查看每個(gè)類(lèi)具體實(shí)現(xiàn)的偽代碼

1. MMTableViewCellInfo的實(shí)現(xiàn)

先看下偽代碼(因封裝的方法較多,這里只分析一個(gè)方法):


MMTableViewCellInfo normalcell.png

分析轉(zhuǎn)化為oc代碼是這樣的,類(lèi)名前綴我使用了LY,注意:demo里對(duì)基礎(chǔ)的cellInfo做了一層封裝。


LYTableViewCellInfo.png

第二個(gè)方法有SEL和target,這里是微信對(duì)cell的選中事件進(jìn)行了處理,使用了target/action方式,所以監(jiān)聽(tīng)cell的點(diǎn)擊時(shí)不需要使用代理,使得每個(gè)cell有自己的action,即做到了解耦,又不用寫(xiě)一堆的if/else了。
微信對(duì)一些信息使用kvc進(jìn)行存取,比如這里的title(textLabel)和rightValue(detailTextLabel),存取的方法在MMTableViewCellInfo的父類(lèi)MMTableViewUserInfo里。
2. MMTableViewSectionInfo的實(shí)現(xiàn)

這個(gè)類(lèi)的實(shí)現(xiàn)相對(duì)簡(jiǎn)單,現(xiàn)在只看cell是如何添加的。

///添加cell
- (void)addCell:(LYTableViewCellInfo *)cell{
    [_arrCells addObject:cell];
}
3. MMTableViewInfo的實(shí)現(xiàn)
- (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"];//通過(guò)LYTableViewCellInfo 父類(lèi)方法kvc獲取到
    cell.detailTextLabel.text = [cellInfo getUserInfoValueForKey:@"rightValue"];//通過(guò)LYTableViewCellInfo 父類(lèi)方法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];//創(chuàng)建cellInfo時(shí),target傳遞并實(shí)現(xiàn)了SEL事件,這里就會(huì)發(fā)送這個(gè)消息,從而實(shí)現(xiàn)cell的點(diǎn)擊事件
        }
    }
}

該類(lèi)里的數(shù)據(jù)來(lái)源就是MMTableViewSectionInfo和MMTableViewCellInfo,前面構(gòu)建好了這兩,這里直接就能用了。
看下最簡(jiǎn)單的調(diào)用示例:

#pragma mark - Creat View
- (void)creatTableView{
    _tableViewInfo = [[LYTableViewInfo alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    [self.view addSubview:[_tableViewInfo getTableView]];
    
    //cell數(shù)據(jù)
    LYTableViewCellInfo *noactionCell = [LYTableViewCellInfo normalCellForTitle:@"無(wú)點(diǎn)擊事件" rightValue:@"沒(méi)有"];
    LYTableViewCellInfo *actionCell = [LYTableViewCellInfo normalCellForSel:@selector(actionCellClick) target:self title:@"有點(diǎn)擊事件" rightValue:@"" accessoryType:UITableViewCellAccessoryDisclosureIndicator];
    
    //section數(shù)據(jù)
    LYTableViewSectionInfo *sectionInfo = [LYTableViewSectionInfo sectionInfoDefaut];

    //添加
    [sectionInfo addCell:noactionCell];
    [sectionInfo addCell:actionCell];
    [_tableViewInfo addSection:sectionInfo];
    
    //刷新
    [[_tableViewInfo getTableView] reloadData];
}

#pragma mark - Event
- (void)actionCellClick{
    NSLog(@"點(diǎn)擊了actionCell");
}

通過(guò)上面一段代碼實(shí)現(xiàn)如下:


baseExample.png

總結(jié):

以最簡(jiǎn)單最基礎(chǔ)的案例介紹了微信的構(gòu)建方式,此方式構(gòu)建滿足了組件的可復(fù)用性、可維護(hù)性、高效性。
這里只是做最簡(jiǎn)單介紹,大家可根據(jù)自己的業(yè)務(wù)需求對(duì)相應(yīng)的方法做調(diào)整,做擴(kuò)展。

倉(cāng)庫(kù)里兩個(gè)Demo,一個(gè)是最基礎(chǔ)的組件,一個(gè)是稍微完善的組件
github:https://github.com/dev-liyang/LYTableViewWidget

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、通過(guò)CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明AI閱讀 16,199評(píng)論 3 119
  • 一:駝背凸腰 駝背凸腰是當(dāng)前很常見(jiàn)的不良身體姿態(tài)現(xiàn)象,特別是在青少年當(dāng)中,其中,極少數(shù)是由于先天遺傳的缺陷,大部分...
    駱長(zhǎng)珊閱讀 1,421評(píng)論 0 1
  • 早上上班,拎著豆?jié){雞蛋坐上出租車(chē),馬上放下豆?jié){,系上安全帶!突然,豆?jié){倒在車(chē)上了,弄得車(chē)上到處都是。 首先我的內(nèi)心...
    上善若水澤萬(wàn)物閱讀 196評(píng)論 2 6
  • 文/李立文 不知道 什么時(shí)侯 丟掉了鄉(xiāng)愁 是因?yàn)?那里 沒(méi)了 父親的烤白薯 沒(méi)了 父親的玉米香 沒(méi)了 父親踏山的腳...
    啟明星子閱讀 272評(píng)論 0 0
  • 運(yùn)用工具是為了更快更好的解決問(wèn)題,可太依賴一個(gè)工具,有時(shí)卻容易陷進(jìn)去,反而成了解決問(wèn)題的障礙。 上周五,處理一個(gè)E...
    魚(yú)兒圓滾滾閱讀 681評(píng)論 0 0

友情鏈接更多精彩內(nèi)容