當(dāng)我們在談cell重用的時候我們在談什么?
好的程序員都是"懶"的.他們在實(shí)現(xiàn)需求的時候,比如說:盡可能的少敲代碼,盡可能的在設(shè)計的時候,考慮以后的需求變動,最后還有就是盡可能的避免出bug...盡管,事實(shí)上,bug總會有的.要想少出bug,對于iOS開發(fā)來說,我覺得還是多了解一些稍微底層的運(yùn)作機(jī)制.比如說當(dāng)你從cell的復(fù)用隊列,出隊一個cell的時候,你知道這個方法做了什么.
cell的重用
- 當(dāng)我們需要使用兩種具有相似布局的cell的時候,我們一般會想到"重用"代碼.這個時候,這個時候我們有一些方式來復(fù)用自己的代碼.比如說:搞一個baseCell抽取公用的部分,然后繼承或者添加分類.但是做這些的目的都是少寫代碼!不會減少最終最后創(chuàng)建對象. 最終,我們只會較少應(yīng)用的安裝包的磁盤容量.而不會減少應(yīng)用運(yùn)行時所占的內(nèi)存.
- 系統(tǒng)也給我們提供了一種復(fù)用的方案,但是這里"復(fù)用"的概念,和上面的代碼復(fù)用是完全不同的兩個概念.這里的復(fù)用是內(nèi)存中對象的復(fù)用. 可以這么理解.對于應(yīng)用來說,誰占用的內(nèi)存少,誰的"生命力"就頑強(qiáng).無論是操作系統(tǒng)還是用戶都不太喜歡,大內(nèi)存應(yīng)用.
- 回到主題!我們在談的cell復(fù)用是指什么,我想更多的是避免對象重復(fù)創(chuàng)建.其所指的是內(nèi)存復(fù)用. 但是某些時候,我們對內(nèi)存的復(fù)用要適可而止.比如說:你不應(yīng)該在cell的動態(tài)的創(chuàng)建太多的布局,然后復(fù)用的時候,像cell內(nèi)傳入不同的數(shù)據(jù)源,來動態(tài)創(chuàng)建和布局cell內(nèi)部的子控件. 相反,應(yīng)該再自定義一個類型的cell,并向tableView注冊一個identifier.只需要創(chuàng)建一個復(fù)用對象,你可以讓CPU少做那么多事情.
談一下UITableViewCell的復(fù)用原理
- 等高cell
- 不等高cell
一般情況下,除了簡單的展示基本的描述性標(biāo)題內(nèi)容,(如iPhone手機(jī)中的設(shè)置應(yīng)用.),我們使用tableView都是自定義不等高的tableViewCell.我們也可以自定義不等高cell. 相對來說自定義不等高cell,布局和計算高度是很消耗性能的. 這是因?yàn)橄到y(tǒng)在顯示不等高cell的過程是這樣的.
- 第一步調(diào)用數(shù)據(jù)源方法(- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;)得到一個cell對象.
- 第二步調(diào)用代理方法(- (CGFloat)tableView:(UITableView )tableView heightForRowAtIndexPath:(NSIndexPath )indexPath;)獲取cell的高度.
雖然核心的方法就這兩個,但是每個cell都是這么調(diào)用的話,再加上如果計算cell高度的方法過于臃腫那么性能堪憂...如果不得不這么做的話,要注意性能優(yōu)化.這個以后再介紹. 如果僅僅是等高cell的話,只需要在創(chuàng)建tableView的時候,統(tǒng)一設(shè)置一下cell的高度就OK了.
/**統(tǒng)一設(shè)置tableViewCell的高度.但是如果實(shí)現(xiàn)了計算cell高度的代理方法仍然會以tableView代理方法的覆蓋值為準(zhǔn)
*/
UITableView* tableView = [[UITableView alloc]initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStylePlain];
//通過tableView的rowHeight屬性,統(tǒng)一設(shè)置cell高度.
tableView.rowHeight = 40.0;
/**除此之外,sectionFooterHeight,sectionHeaderHeight都可以這樣統(tǒng)一設(shè)置高度
*/
self.tableView.sectionHeaderHeight = 10.0f;
self.tableView.sectionFooterHeight = 10.0f;
在談tableView是如何復(fù)用tableViewCell的原理之前,先看一下如何重用一個UITableViewCell?
- 第一種方式
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
return cell;
}
這是iOS 6.0之后的方法,這種方法需要提前使用- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString )identifier NS_AVAILABLE_IOS(6_0); 方法注冊,否則在使用第一種方式從內(nèi)存復(fù)用池中取的時候,會崩!使用這種方式,只需要注冊cell,就可以了. 在使用的過程中,直接從復(fù)用隊列直接出隊特定identifier*類型的cell就可以了.不需要檢查是否為空,當(dāng)你調(diào)用這個方法的時,這些操作,tableView的復(fù)用機(jī)制已經(jīng)為你做好了.
- 第二種重用的方式
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identify];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identify];
}
return cell;
}
使用第二種方法的時候,要檢查cell是否為空,當(dāng)cell為空的時候,需要你手動調(diào)用cell的指定初始化方法來獲取一個cell,并傳入identifier,這個方法會自動向tableView注冊一個特定類型的cell.事實(shí)上,方法一也是這么做的.我們在自定義cell的時候,覆蓋系統(tǒng)指定初始化方法.復(fù)用的該方法被系統(tǒng)調(diào)用并自動注冊一個identifier.
這是cell能被復(fù)用的重要原因
Designated initializer. If the cell can be reused, you must pass in a reuse identifier. You should use the same reuse identifier for all cells of the same form.
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier NS_AVAILABLE_IOS(3_0) NS_DESIGNATED_INITIALIZER;
UITableViewCell復(fù)用介紹
實(shí)在不敢,加上"原理"兩個字,介紹一下我的理解.每個tableView在創(chuàng)建的時候都會,有一個復(fù)用池(底層就是一塊內(nèi)存,也即是放復(fù)用cell的地方),這個復(fù)用池在剛開始的時候是空的.即沒有復(fù)用cell在里面.當(dāng)復(fù)用池里面有許多個cell的時候,它們這些復(fù)用cell是被一個隊列管理的.蘋果肯定對這個隊列做了一些優(yōu)化機(jī)制,比如說可以讓你很快的根據(jù)一個"identifier"從復(fù)用池中快速出隊一個特定的復(fù)用cell.
- 復(fù)用池中的reuse cell 是什么時候被添加的.
- 當(dāng)我們使用第一種方式復(fù)用的時候,注冊時向復(fù)用池添加reuse cell .
//不僅僅cell可以復(fù)用,sectionHeaderView, sectionFooterView也可以.甚至通過XIB創(chuàng)建的對象都可以
- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier
//復(fù)用sectionHeaderFooterView也需要向tableView注冊
- (void)registerClass:(nullable Class)aClass forHeaderFooterViewReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
- 當(dāng)我們使用第二種方式復(fù)用cell的時候,創(chuàng)建cell被第一次創(chuàng)建的時候根據(jù)identifier自動向tableView注冊.
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier NS_AVAILABLE_IOS(3_0) NS_DESIGNATED_INITIALIZER;
總結(jié):復(fù)用就是從復(fù)用池(就是一塊內(nèi)存中有許多用過的已注冊cell),中根據(jù)identifier取出一個特定的cell.
- 取出的cell有沒有被初始化.
- tableView利用它的復(fù)用機(jī)制,避免了我們重復(fù)創(chuàng)建cell.試想如果沒有復(fù)用機(jī)制,我們要么需要不斷的創(chuàng)建cell,不銷毀. 要么就是不斷的創(chuàng)建,不斷的銷毀.tableView復(fù)用則是讓我們,需要cell的時候,從復(fù)用池中去取,你只需要告訴它你需要什么類型的cell就好了.這時候會出現(xiàn)兩個情況,第一種是沒取到.然后系統(tǒng)根據(jù)它的復(fù)用機(jī)制(第二種使用方式),自動的創(chuàng)建了一個cell并注冊了,添加到復(fù)用池.這時候,下次再從復(fù)用池去取這個類型的cell時候,復(fù)用池因?yàn)橐呀?jīng)存在,所以直接返回上次使用的cell.注意此時返回的是上次使用后完整的狀態(tài),也就是說已經(jīng)初始化了.
- 當(dāng)我們需要一個"干凈的"的cell時候,我們需要自己手動去清除這個剛剛從復(fù)用池中取到的cell,尤其是我們在自定義cell里面做了太多自定義布局子控件的操作,這個時候,你需要移除所有的子控件.或者在自定義cell內(nèi)部做一些操作.
- 每次當(dāng)我們?nèi)〉脧?fù)用cell的時候,cell會調(diào)用其內(nèi)部的一個方法
//需要調(diào)用其父方法,每次返回復(fù)用cell都會調(diào)用這個方法,我們可以在這個方法來清除不需要的子控件.
- (void)prepareForReuse NS_REQUIRES_SUPER; // if the cell is reusable (has a reuse identifier), this is called just before the cell is returned from the table view method dequeueReusableCellWithIdentifier:. If you override, you MUST call super.
最后,幾點(diǎn)建議:適量組織代碼,避免在cell內(nèi)部反復(fù)操作子控件,應(yīng)該重新注冊一個特定類型的cell.每次從復(fù)用池中取出cell的時候,注意初始化(清除干凈上次的殘留物).不要濫用復(fù)用機(jī)制,注冊太多種類的cell