原文:UITableView的性能優(yōu)化,提升列表滾動(dòng)的流暢性
本篇博客目的是:總結(jié)UITableView性能優(yōu)化方向,有些優(yōu)化方式是我在開發(fā)中使用到過的,有些是沒有用到過的,當(dāng)中也有參考、查看很多大神的總結(jié),這才有了本篇博客,僅供大家參考!
1.Cell重用
1.1>數(shù)據(jù)源方法優(yōu)化
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath;
在可見的頁面會重復(fù)繪制頁面,每次刷新顯示都會去創(chuàng)建新的Cell,非常耗費(fèi)性能。
解決方案:首先創(chuàng)建一個(gè)靜態(tài)變量reuseID(代理方法返回Cell會調(diào)用很多次,防止重復(fù)創(chuàng)建,static保證只會被創(chuàng)建一次,提高性能),然后,從緩存池中取相應(yīng)identifier的Cell并更新數(shù)據(jù),如果沒有,才開始alloc新的Cell,并用identifier標(biāo)識Cell。每個(gè)Cell都會注冊一個(gè)identifier(重用標(biāo)識符)放入緩存池,當(dāng)需要調(diào)用的時(shí)候就直接從緩存池里找對應(yīng)的id,當(dāng)不需要時(shí)就放入緩存池等待調(diào)用。(移出屏幕的Cell才會放入緩存池中,并不會被release)所以在數(shù)據(jù)源方法中做出如下優(yōu)化:
// 調(diào)用次數(shù)太多,static 保證只創(chuàng)建一次reuseID,提高性能
staticNSString*reuseID = “reuseCellID”;
// 緩存池中取已經(jīng)創(chuàng)建的cell
UITableViewCell*cell = [tableView dequeueReusableCellWithIdentifier:reuseID];
1.2>緩存池的實(shí)現(xiàn)
當(dāng)Cell要alloc時(shí),UITableView會在堆中開辟一段內(nèi)存以供Cell緩存之用。Cell的重用通過identifier標(biāo)識不同類型的Cell,由此可以推斷出,緩存池外層可能是一個(gè)可變字典,通過key來取出內(nèi)部的Cell,而緩存池為存儲不同高度、不同類型(包含圖片、Label等)的Cell,可以推斷出緩存池的字典內(nèi)部可能是一個(gè)可變數(shù)組,用來存放不同類型的Cell,緩存池中只會保存已經(jīng)被移出屏幕的不同類型的Cell。
1.3>緩存池獲取可重用Cell兩個(gè)方法的區(qū)別
-(nullable__kindofUITableViewCell*)dequeueReusableCellWithIdentifier:(NSString*)identifier;
這個(gè)方法會查詢可重用Cell,如果注冊了原型Cell,能夠查詢到,否則,返回nil;而且需要判斷if(cell == nil),才會創(chuàng)建Cell,不推薦
-(__kindofUITableViewCell*)dequeueReusableCellWithIdentifier:(NSString*)identifier forIndexPath:(NSIndexPath*)indexPath NS_AVAILABLE_IOS(6_0);
使用這個(gè)方法之前,必須通過xib(storyboard)或是Class(純代碼)注冊可重用Cell,而且這個(gè)方法一定會返回一個(gè)Cell
注冊Cell
-(void)registerNib:(nullableUINib*)nib forCellReuseIdentifier:(NSString*)identifier NS_AVAILABLE_IOS(5_0);
-(void)registerClass:(nullableClass)cellClass forCellReuseIdentifier:(NSString*)identifier NS_AVAILABLE_IOS(6_0);
好處:如果緩沖區(qū) Cell 不存在,會使用原型 Cell 實(shí)例化一個(gè)新的 Cell,不需要再判斷,同時(shí)代碼結(jié)構(gòu)更清晰。
2. 定義一種(盡量少)類型的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的’體積’可能會大點(diǎn),但是因?yàn)镃ell的數(shù)量不會很多,完全可以接受的。好處:
減少代碼量,減少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í)最多可能會是M x N 個(gè)Cell的實(shí)例,雖然可能并不會占用太多內(nèi)存,但是能少點(diǎn)不是更好嗎。
2.2>善用hidden隱藏(顯示)subviews
只定義一種Cell,那該如何顯示不同類型的內(nèi)容呢?答案就是,把所有不同類型的view都定義好,放在cell里面,通過hidden顯示、隱藏,來顯示不同類型的內(nèi)容。畢竟,在用戶快速滑動(dòng)中,只是單純的顯示、隱藏subview比實(shí)時(shí)創(chuàng)建要快得多。
3. 提前計(jì)算并緩存Cell的高度
在iOS中,不設(shè)UITableViewCell的預(yù)估行高的情況下,會優(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è)問題的時(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ù)。
在上面的基礎(chǔ)上,還可以繼續(xù)進(jìn)行優(yōu)化,提前創(chuàng)建真正顯示的、需要加工的數(shù)據(jù)并緩存。不過這方面優(yōu)化我好像之前沒有接觸。大家可以去看看這篇博客,其實(shí)本篇性能優(yōu)化也借鑒了好多這篇文章,這是土土大神的博客地址,大家可以進(jìn)去看看,好多iOS開發(fā)的知識,當(dāng)然也有我參照的這篇博客。http://tutuge.me
4.異步繪制(自定義Cell繪制)
遇到比較復(fù)雜的界面的時(shí)候,如復(fù)雜點(diǎn)的圖文混排,上面的那種優(yōu)化行高的方式可能就不能滿足要求了,當(dāng)然了,由于我的開發(fā)經(jīng)驗(yàn)尚短,說實(shí)話,還沒遇到要將自定義的Cell重新繪制。至于這方面,大家可以參考這篇博客,絕對是開發(fā)經(jīng)驗(yàn)十足的大神,分享足夠多的UITableView方面的性能優(yōu)化,好多借鑒自這里,我都不好意思了。http://www.cocoachina.com/ios/20150602/11968.html
5.滑動(dòng)時(shí),按需加載
開發(fā)的過程中,自定義Cell的種類千奇百怪,但Cell本來就是用來顯示數(shù)據(jù)的,不說100%帶有圖片,也差不多,這個(gè)時(shí)候就要考慮,下滑的過程中可能會有點(diǎn)卡頓,尤其網(wǎng)絡(luò)不好的時(shí)候,異步加載圖片是個(gè)程序員都會想到,但是如果給每個(gè)循環(huán)對象都加上異步加載,開啟的線程太多,一樣會卡頓,我記得好像線程條數(shù)一般3-5條,最多也就6條吧。這個(gè)時(shí)候利用UIScrollViewDelegate兩個(gè)代理方法就能很好地解決這個(gè)問題。
-(void)scrollViewDidEndDragging:(UIScrollView*)scrollView willDecelerate:(BOOL)decelerate
-(void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView
思想就是識別UITableView禁止或者減速滑動(dòng)結(jié)束的時(shí)候,進(jìn)行異步加載圖片,快滑動(dòng)過程中,只加載目標(biāo)范圍內(nèi)的Cell,這樣按需加載,極大的提高流暢度。而SDWebImage可以實(shí)現(xiàn)異步加載,與這條性能配合就完美了,尤其是大量圖片展示的時(shí)候。而且也不用擔(dān)心圖片緩存會造成內(nèi)存警告的問題。
//獲取可見部分的Cell
NSArray*visiblePaths = [self.tableViewindexPathsForVisibleRows];
for(NSIndexPath*indexPath in visiblePaths)? ? ??
{
????? //獲取的dataSource里面的對象,并且判斷加載完成的不需要再次異步加載? ? ? ?
}
記得在記得在“tableView:cellForRowAtIndexPath:”方法中加入判斷:
// tableView 停止滑動(dòng)的時(shí)候異步加載圖片
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
??? if(self.tableView.dragging==NO&&self.tableView.decelerating==NO) { ? ? ? ??
? ? ? ? //開始異步加載圖片
??? }?
}
6.緩存View
當(dāng)Cell中的部分View是非常獨(dú)立的,并且不便于重用的,而且“體積”非常小,在內(nèi)存可控的前提下,我們完全可以將這些view緩存起來。當(dāng)然也是緩存在模型中。
7.避免大量的圖片縮放、顏色漸變等,盡量顯示“大小剛好合適的圖片資源”
8.避免同步的從網(wǎng)絡(luò)、文件獲取數(shù)據(jù),Cell內(nèi)實(shí)現(xiàn)的內(nèi)容來自web,使用異步加載,緩存請求結(jié)果
9.渲染
9.1>減少subviews的個(gè)數(shù)和層級
子控件的層級越深,渲染到屏幕上所需要的計(jì)算量就越大;如多用drawRect繪制元素,替代用view顯示
9.2>少用subviews的透明圖層
對于不透明的View,設(shè)置opaque為YES,這樣在繪制該View時(shí),就不需要考慮被View覆蓋的其他內(nèi)容(盡量設(shè)置Cell的view為opaque,避免GPU對Cell下面的內(nèi)容也進(jìn)行繪制)
9.3>避免CALayer特效(shadowPath)
給Cell中View加陰影會引起性能問題,如下面代碼會導(dǎo)致滾動(dòng)時(shí)有明顯的卡頓:
view.layer.shadowColor= color.CGColor;
view.layer.shadowOffset= offset;
view.layer.shadowOpacity=1;
view.layer.shadowRadius= radius;
總結(jié):UITableView的優(yōu)化主要從三個(gè)方面入手:
提前計(jì)算并緩存好高度(布局),因?yàn)閔eightForRowAtIndexPath:是調(diào)用最頻繁的方法;(這個(gè)是開發(fā)中肯定會要優(yōu)化的,不可能一個(gè)app就幾個(gè)Cell吧)
滑動(dòng)時(shí)按需加載,防止卡頓,這個(gè)我也認(rèn)為是很有必要做的性能優(yōu)化,配合SDWebImage
異步繪制,遇到復(fù)雜界面,遇到性能瓶頸時(shí),可能就是突破口(如題,遇到復(fù)雜的界面,可以從這入手)
緩存一切可以緩存的,這個(gè)在開發(fā)的時(shí)候,往往是性能優(yōu)化最多的方向