- Cell重用
1.1>數(shù)據(jù)源方法優(yōu)化
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
在可見(jiàn)的頁(yè)面會(huì)重復(fù)繪制頁(yè)面,每次刷新顯示都會(huì)去創(chuàng)建新的Cell,非常耗費(fèi)性能。
解決方案:首先創(chuàng)建一個(gè)靜態(tài)變量reuseID(代理方法返回Cell會(huì)調(diào)用很多次,防止重復(fù)創(chuàng)建,static保證只會(huì)被創(chuàng)建一次,提高性能),然后,從緩存池中取相應(yīng)identifier的Cell并更新數(shù)據(jù),如果沒(méi)有,才開(kāi)始alloc新的Cell,并用identifier標(biāo)識(shí)Cell。每個(gè)Cell都會(huì)注冊(cè)一個(gè)identifier(重用標(biāo)識(shí)符)放入緩存池,當(dāng)需要調(diào)用的時(shí)候就直接從緩存池里找對(duì)應(yīng)的id,當(dāng)不需要時(shí)就放入緩存池等待調(diào)用。(移出屏幕的Cell才會(huì)放入緩存池中,并不會(huì)被release)所以在數(shù)據(jù)源方法中做出如下優(yōu)化:
// 調(diào)用次數(shù)太多,static 保證只創(chuàng)建一次reuseID,提高性能
static NSString *reuseID = “reuseCellID”;
// 緩存池中取已經(jīng)創(chuàng)建的cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseID];
1.2>緩存池的實(shí)現(xiàn)
當(dāng)Cell要alloc時(shí),UITableView會(huì)在堆中開(kāi)辟一段內(nèi)存以供Cell緩存之用。Cell的重用通過(guò)identifier標(biāo)識(shí)不同類型的Cell,由此可以推斷出,緩存池外層可能是一個(gè)可變字典,通過(guò)key來(lái)取出內(nèi)部的Cell,而緩存池為存儲(chǔ)不同高度、不同類型(包含圖片、Label等)的Cell,可以推斷出緩存池的字典內(nèi)部可能是一個(gè)可變數(shù)組,用來(lái)存放不同類型的Cell,緩存池中只會(huì)保存已經(jīng)被移出屏幕的不同類型的Cell。
1.3>緩存池獲取可重用Cell兩個(gè)方法的區(qū)別
-(nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
這個(gè)方法會(huì)查詢可重用Cell,如果注冊(cè)了原型Cell,能夠查詢到,否則,返回nil;而且需要判斷if(cell == nil),才會(huì)創(chuàng)建Cell,不推薦。
-(__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);
使用這個(gè)方法之前,必須通過(guò)xib(storyboard)或是Class(純代碼)注冊(cè)可重用Cell,而且這個(gè)方法一定會(huì)返回一個(gè)Cell
注冊(cè)Cell - (void)registerNib:(nullable UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);
- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
好處:如果緩沖區(qū) Cell 不存在,會(huì)使用原型 Cell 實(shí)例化一個(gè)新的 Cell,不需要再判斷,同時(shí)代碼結(jié)構(gòu)更清晰。
- 定義一種(盡量少)類型的Cell及善用hidden隱藏(顯示)subviews
2.1>一種類型的Cell
分析Cell結(jié)構(gòu),盡可能的將 相同內(nèi)容的抽取到一種樣式Cell中,前面已經(jīng)提到了Cell的重用機(jī)制,這樣就能保證UITbaleView要顯示多少內(nèi)容,真正創(chuàng)建出的Cell可能只比屏幕顯示的Cell多一點(diǎn)。雖然Cell的’體積’可能會(huì)大點(diǎn),但是因?yàn)镃ell的數(shù)量不會(huì)很多,完全可以接受的。好處:
- 減少代碼量,減少Nib文件的數(shù)量,統(tǒng)一一個(gè)Nib文件定義Cell,容易修改、維護(hù)
- 基于Cell的重用,真正運(yùn)行時(shí)鋪滿屏幕所需的Cell數(shù)量大致是固定的,設(shè)為N個(gè)。所以如果如果只有一種Cell,那就是只有N個(gè)Cell的實(shí)例;但是如果有M種Cell,那么運(yùn)行時(shí)最多可能會(huì)是“M x N = MN”個(gè)Cell的實(shí)例,雖然可能并不會(huì)占用太多內(nèi)存,但是能少點(diǎn)不是更好嗎。
2.2>善用hidden隱藏(顯示)subviews
只定義一種Cell,那該如何顯示不同類型的內(nèi)容呢?答案就是,把所有不同類型的view都定義好,放在cell里面,通過(guò)hidden顯示、隱藏,來(lái)顯示不同類型的內(nèi)容。畢竟,在用戶快速滑動(dòng)中,只是單純的顯示、隱藏subview比實(shí)時(shí)創(chuàng)建要快得多。
- 提前計(jì)算并緩存Cell的高度
在iOS中,不設(shè)UITableViewCell的預(yù)估行高的情況下,會(huì)優(yōu)先調(diào)用”tableView:heightForRowAtIndexPath:”方法,獲取每個(gè)Cell的即將顯示的高度,從而確定UITableView的布局,實(shí)際就是要獲取contentSize(UITableView繼承自UIScrollView,只有獲取滾動(dòng)區(qū)域,才能實(shí)現(xiàn)滾動(dòng)),然后才調(diào)用”tableView:cellForRowAtIndexPath”,獲取每個(gè)Cell,進(jìn)行賦值。如果項(xiàng)目中模塊有10000個(gè)Cell需要顯示,可想而知…
解決方案:我個(gè)人認(rèn)為,可以創(chuàng)建一個(gè)frame模型,提前計(jì)算每個(gè)Cell的高度。參考其中一篇博客的時(shí)候,在解決這個(gè)問(wèn)題的時(shí)候,可以將計(jì)算Cell的高度放入數(shù)據(jù)模型,但這與MVC設(shè)計(jì)模式可能稍微有點(diǎn)沖突,這個(gè)時(shí)候我就想到MVVM這種設(shè)計(jì)模式,這個(gè)時(shí)候才能稍微有點(diǎn)MVVM這種設(shè)計(jì)模式的優(yōu)點(diǎn)(其實(shí)還是很不理解的),可以講計(jì)算Cell高度放入ViewModel(視圖模型)中,讓Model(數(shù)據(jù)模型)只負(fù)責(zé)處理數(shù)據(jù)。
4.異步繪制(自定義Cell繪制)
遇到比較復(fù)雜的界面的時(shí)候,如復(fù)雜點(diǎn)的圖文混排,上面的那種優(yōu)化行高的方式可能就不能滿足要求了,當(dāng)然了,由于我的開(kāi)發(fā)經(jīng)驗(yàn)尚短,說(shuō)實(shí)話,還沒(méi)遇到要將自定義的Cell重新繪制。
5.滑動(dòng)時(shí),按需加載
開(kāi)發(fā)的過(guò)程中,自定義Cell的種類千奇百怪,但Cell本來(lái)就是用來(lái)顯示數(shù)據(jù)的,不說(shuō)100%帶有圖片,也差不多,這個(gè)時(shí)候就要考慮,下滑的過(guò)程中可能會(huì)有點(diǎn)卡頓,尤其網(wǎng)絡(luò)不好的時(shí)候,異步加載圖片是個(gè)程序員都會(huì)想到,但是如果給每個(gè)循環(huán)對(duì)象都加上異步加載,開(kāi)啟的線程太多,一樣會(huì)卡頓,我記得好像線程條數(shù)一般3-5條,最多也就6條吧。這個(gè)時(shí)候利用UIScrollViewDelegate兩個(gè)代理方法就能很好地解決這個(gè)問(wèn)題。
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
6.緩存View
當(dāng)Cell中的部分View是非常獨(dú)立的,并且不便于重用的,而且“體積”非常小,在內(nèi)存可控的前提下,我們完全可以將這些view緩存起來(lái)。當(dāng)然也是緩存在模型中。
7.避免大量的圖片縮放、顏色漸變等,盡量顯示“大小剛好合適的圖片資源”
8.避免同步的從網(wǎng)絡(luò)、文件獲取數(shù)據(jù),Cell內(nèi)實(shí)現(xiàn)的內(nèi)容來(lái)自web,使用異步加載,緩存請(qǐng)求結(jié)果
9.渲染
9.1>減少subviews的個(gè)數(shù)和層級(jí)
子控件的層級(jí)越深,渲染到屏幕上所需要的計(jì)算量就越大;如多用drawRect繪制元素,替代用view顯示
9.2>少用subviews的透明圖層
對(duì)于不透明的View,設(shè)置opaque為YES,這樣在繪制該View時(shí),就不需要考慮被View覆蓋的其他內(nèi)容(盡量設(shè)置Cell的view為opaque,避免GPU對(duì)Cell下面的內(nèi)容也進(jìn)行繪制)
9.3>避免CALayer特效(shadowPath)
給Cell中View加陰影會(huì)引起性能問(wèn)題,如下面代碼會(huì)導(dǎo)致滾動(dòng)時(shí)有明顯的卡頓:
view.layer.shadowColor = color.CGColor;
view.layer.shadowOffset = offset;
view.layer.shadowOpacity = 1;
view.layer.shadowRadius = radius;
僅用來(lái)學(xué)習(xí)。