背景:
在搭建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)容:2.MMTableViewSectionInfo
通過(guò)觀察阁簸,很容易看出MMTableViewInfo中的成員變量_arrSections是_tableView的數(shù)據(jù)源,調(diào)試打印其元素是MMTableViewSectionInfo對(duì)象哼丈。下圖是MMTableViewSectionInfo頭文件截圖部分內(nèi)容:3.MMTableViewCellInfo
通過(guò)觀察启妹,猜測(cè)MMTableViewSectionInfo中的_arrCells應(yīng)該是每個(gè)section中的cell數(shù)組,調(diào)試打印其元素是MMTableViewCellInfo對(duì)象醉旦。下圖是MMTableViewCellInfo頭文件截圖部分內(nèi)容:二饶米、通過(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è)方法):
分析轉(zhuǎn)化為oc代碼是這樣的,類(lèi)名前綴我使用了LY乌妙,注意:demo里對(duì)基礎(chǔ)的cellInfo做了一層封裝使兔。
第二個(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)如下:
總結(jié):
以最簡(jiǎn)單最基礎(chǔ)的案例介紹了微信的構(gòu)建方式崇堰,此方式構(gòu)建滿(mǎn)足了組件的可復(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