IOS之UITableView性能優(yōu)化

前言

????????說起優(yōu)化,簡直是博大精深。話不多說,筆者今天梳理的內(nèi)容,UITableView的性能優(yōu)化。先說一下tableview的執(zhí)行順序:

1.它會調(diào)用代理方法確定有幾個分區(qū)

numberOfSectionsInTableView:

2.確定每個分區(qū)的表頭高和表尾高(如果設定了HeardView和FooterView)

heightForHeaderInSection:

tableView:heightForFooterInSection:

3.確定每個分區(qū)有多少的cell

numberOfRowsInSection:

4.然后確定cell的高度

heightForRowAtIndexPath:

如果有多個section和row則循環(huán)執(zhí)行上面的代碼

5.以上信息確定完畢后及調(diào)用代理方法去獲取cell

cellForRowAtIndexPath:

6.返回cell的高度

heightForRowAtIndexPath:

7.cell將要顯示到屏幕上

willDisplayCell:forRowAtIndexPath:

8.cell超出屏幕進行服用時及會調(diào)用兩次

heightForRowAtIndexPath:

然后在進行調(diào)用 5 . 6. 7 方法

一、cell的復用

? ? ????首先cell有兩種復用方法:

????????????????- (id)dequeueReusableCellWithIdentifier:(NSString?*)identifier; ?

????????????????-?(id)dequeueReusableCellWithIdentifier:(NSString?*)identifier?forIndexPath:(NSIndexPath?*)indexPath; ?

????????在iOS 6中dequeueReusableCellWithIdentifier:被dequeueReusableCellWithIdentifier:forIndexPath:所取代。如此一來,在表格視圖中創(chuàng)建并添加UITableViewCell對象會變得更為精簡而流暢。而且使用dequeueReusableCellWithIdentifier:forIndexPath:一定會返回cell,系統(tǒng)在默認沒有cell可復用的時候會自動創(chuàng)建一個新的cell出來。

1.dequeueReusableCellWithIdentifier:(NSString?*)identifier,如圖:


2.dequeueReusableCellWithIdentifier:(NSString?*)identifier?forIndexPath:(NSIndexPath?*)indexPath,如圖:

????????獲取cell時如果沒有可重用cell,如果cell為nib,將創(chuàng)建新的cell并調(diào)用其中的awakeFromNib方法;否則

調(diào)用cell中的initWithStyle:withReuseableCellIdentifier:方法創(chuàng)建新的cell。不過此方法,在創(chuàng)建tableview的時候,要注冊cell,如圖:


? ? ? ? 不論以上哪兩種方法,我們獲取到cell后,可能習慣在cellForRowAtIndexPath:中為每一個cell綁定數(shù)據(jù),實際上在調(diào)用cellForRowAtIndexPath:的時候cell還沒有被顯示出來,為了提高效率我們應該把數(shù)據(jù)綁定的操作放在cell顯示出來后再執(zhí)行,可以在tableView:willDisplayCell:forRowAtIndexPath:(以后簡稱willDisplayCell)方法中綁定數(shù)據(jù)。

????????*注意*:

????????willDisplayCell在cell 在tableview展示之前就會調(diào)用,此時cell實例已經(jīng)生成,所以不能更改cell的結(jié)構(gòu),只能是改動cell上的UI的一些屬性(例如label的內(nèi)容等)。


二、cell的高度

????????說到cell高度優(yōu)化問題,可能大家都知道去計算并緩存cell的高度。今天說另一種方法。我們分為兩種cell,一種是定高的cell,另外一種是動態(tài)高度的cell。

????????(1)定高的cell,應該采用如下方式:

????????self.tableView.rowHeight = 100;

????????這個方法指定了所有cell高度都是100的tableview,rowHeight默認的值是44,所以一個空的TableView會顯示成這個樣子。對于定高cell,直接采用上面方式給定高度,不需要實現(xiàn)tableView:heightForRowAtIndexPath:以節(jié)省不必要的計算和開銷。

????????(2)動態(tài)高度的cell

????????我們需要實現(xiàn)它的代理,來給出高度:

????????-(CGFloat)tableView:(UITableView*)tableViewheightForRowAtIndexPath:(NSIndexPath*)indexPath{

????????????????//return xxx;

????????}

????????這個代理方法實現(xiàn)后,上面的rowHeight的設置將會變成無效。在這個方法中,我們需要提高cell高度的計算效率,來節(jié)省時間。自從iOS8之后有了Self-Sizing cell的概念,cell可以自己算出高度,IOS11以后,Self-Sizing默認開啟,包括Headers, footers。

????????使用self-sizing cell需要滿足以下三個條件:

????????(1)使用Autolayout進行UI布局約束(要求cell.contentView的四條邊都與內(nèi)部元素有約束關系)。

????????(2)指定TableView的estimatedRowHeight屬性的默認值。

????????(3)指定TableView的rowHeight屬性為UITableViewAutomaticDimension。

????????- (void)viewDidload {

????????????self.myTableView.estimatedRowHeight = 44.0;

????????????self.myTableView.rowHeight = UITableViewAutomaticDimension;

????????}

三、cell的渲染

????????為了保證TableView的流暢,當快速滑動的時候,cell必須被快速的渲染出來。所以cell渲染的速度必須快。如何提高cell的渲染速度呢?

????????(1)當有圖像時,預渲染圖像,在bitmap context先將其畫一遍,導出成UIImage對象,然后再繪制到屏幕,這會大大提高渲染速度。具體內(nèi)容可以自行查找“利用預渲染加速顯示iOS圖像”相關資料

????????(2)渲染最好時的操作之一就是混合(blending)了,所以我們不要使用透明背景,將cell的opaque值設為Yes,背景色不要使用clearColor,盡量不要使用陰影漸變等;

????????(3)由于混合操作是使用GPU來執(zhí)行,我們可以用CPU來渲染,這樣混合操作就不再執(zhí)行??梢栽赨IView的drawRect方法中自定義繪制;

????????(4)減少subviews的個數(shù)和層級。子控件的層級越深,渲染到屏幕上所需要的計算量就越大;如多用drawRect繪制元素,替代用view顯示;

????????(5)少用subviews的透明圖層。對于不透明的View,設置opaque為YES,這樣在繪制該View時,就不需要考慮被View覆蓋的其他內(nèi)容(盡量設置Cell的view為opaque,避免GPU對Cell下面的內(nèi)容也進行繪制);

?????????(6)避免CALayer特效(shadowPath)。 給Cell中View加陰影會引起性能問題,如下面代碼會導致滾動時有明顯的卡頓:

????????view.layer.shadowColor= color.CGColor;

????????view.layer.shadowOffset= offset;

????????view.layer.shadowOpacity=1;

????????view.layer.shadowRadius= radius;

????????(7)我們在cell上添加系統(tǒng)控件的時候,實際上系統(tǒng)都會調(diào)用底層的接口進行繪制,大量添加控件時,會消耗很大的資源并且也會影響渲染的性能。當使用默認的UITableViewCell并且在它的ContentView上面添加控件時會相當消耗性能。所以目前最佳的方法還是繼承UITableViewCell,并重寫drawRect方法。

????????(8)在實現(xiàn)drawRect方法的時候,它的參數(shù)rect就是我們需要繪制的區(qū)域,在rect范圍之外的區(qū)域我們不需要進行繪制,否則會消耗相當大的資源。

????????(9)不要給cell動態(tài)添加subView,在初始化cell的時候就將所有需要展示的添加完畢,然后根據(jù)需要來設置hide屬性顯示和隱藏。

????????(10)異步化UI,不要阻塞主線程.

? ? ? ? (11)滑動時按需加載對應的內(nèi)容

? ? ? ? 參考文章

四、離屏渲染

????????下面的情況或操作會引發(fā)離屏渲染:

? ? ? ? (1)、為圖層設置遮罩(layer.mask);

? ? ? ? (2)、將圖層的layer.masksToBounds / view.clipsToBounds屬性設置為true;

? ? ? ? (3)、將圖層layer.allowsGroupOpacity屬性設置為YES和layer.opacity小于1.0;

? ? ? ? (4)、為圖層設置陰影(layer.shadow *);

? ? ? ? (5)、為圖層設置layer.shouldRasterize=true;

? ? ? ? (6)、具有l(wèi)ayer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的圖層;

? ? ? ? (7)、文本(任何種類,包括UILabel,CATextLayer,Core Text等);

? ? ? ? (8)、使用CGContext在drawRect :方法中繪制大部分情況下會導致離屏渲染,甚至僅僅是一個空的實現(xiàn)。

1、優(yōu)化方案

????????官方對離屏渲染產(chǎn)生性能問題也進行了優(yōu)化:

????????iOS 9.0 之前UIimageView跟UIButton設置圓角都會觸發(fā)離屏渲染。iOS 9.0 之后UIButton設置圓角會觸發(fā)離屏渲染,而UIImageView里png圖片設置圓角不會觸發(fā)離屏渲染了,如果設置其他陰影效果之類的還是會觸發(fā)離屏渲染的。

優(yōu)化方案1:使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個圓角

????????UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];

????????imageView.image = [UIImage imageNamed:@"myImg"];

????????//開始對imageView進行畫圖

????????UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);

????????//使用貝塞爾曲線畫出一個圓形圖

????????[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];

????????[imageView drawRect:imageView.bounds];

????????imageView.image = UIGraphicsGetImageFromCurrentImageContext();

????????//結(jié)束畫圖

????????UIGraphicsEndImageContext();

????????[self.view addSubview:imageView];

優(yōu)化方案2:使用CAShapeLayer和UIBezierPath設置圓角

????????UIImageView *imageView=[[UIImageViewalloc]initWithFrame:CGRectMake(100,100,100,100)];

????????imageView.image=[UIImageimageNamed:@"myImg"];

????????UIBezierPath *maskPath=[UIBezierPathbezierPathWithRoundedRect:imageView.boundsbyRoundingCorners:UIRectCornerAllCornerscornerRadii:imageView.bounds.size];

????????CAShapeLayer *maskLayer=[[CAShapeLayeralloc]init];

????????//設置大小

????????maskLayer.frame=imageView.bounds;

????????//設置圖形樣子

????????maskLayer.path=maskPath.CGPath;

????????imageView.layer.mask=maskLayer;

????????[self.viewaddSubview:imageView];

????????對于方案2需要解釋的是:

????????CAShapeLayer繼承于CALayer,可以使用CALayer的所有屬性值;CAShapeLayer需要貝塞爾曲線配合使用才有意義(也就是說才有效果);使用CAShapeLayer(屬于CoreAnimation)與貝塞爾曲線可以實現(xiàn)不在view的drawRect(繼承于CoreGraphics走的是CPU,消耗的性能較大)方法中畫出一些想要的圖形,CAShapeLayer動畫渲染直接提交到手機的GPU當中,相較于view的drawRect方法使用CPU渲染而言,其效率極高,能大大優(yōu)化內(nèi)存使用情況??偟膩碚f就是用CAShapeLayer的內(nèi)存消耗少,渲染速度快,建議使用優(yōu)化方案2。

(2)shadow優(yōu)化

????????對于shadow,如果圖層是個簡單的幾何圖形或者圓角圖形,我們可以通過設置shadowPath來優(yōu)化性能,能大幅提高性能。示例如下:

????????imageView.layer.shadowColor=[UIColorgrayColor].CGColor;

????????imageView.layer.shadowOpacity=1.0;

????????imageView.layer.shadowRadius=2.0;

????????UIBezierPath *path=[UIBezierPathbezierPathWithRect:imageView.frame];

????????imageView.layer.shadowPath=path.CGPath;

????????我們還可以通過設置shouldRasterize屬性值為YES來強制開啟離屏渲染。其實就是光柵化(Rasterization)。既然離屏渲染這么不好,為什么我們還要強制開啟呢?當一個圖像混合了多個圖層,每次移動時,每一幀都要重新合成這些圖層,十分消耗性能。當我們開啟光柵化后,會在首次產(chǎn)生一個位圖緩存,當再次使用時候就會復用這個緩存。但是如果圖層發(fā)生改變的時候就會重新產(chǎn)生位圖緩存。所以這個功能一般不能用于UITableViewCell中,cell的復用反而降低了性能。最好用于圖層較多的靜態(tài)內(nèi)容的圖形。而且產(chǎn)生的位圖緩存的大小是有限制的,一般是2.5個屏幕尺寸。在100ms之內(nèi)不使用這個緩存,緩存也會被刪除。所以我們要根據(jù)使用場景而定。

(3)其他的一些優(yōu)化建議

? ? ? ? 1.當我們需要圓角效果時,可以使用一張中間透明圖片蒙上去

? ? ? ? 2.使用ShadowPath指定layer陰影效果路徑

? ? ? ? 3.使用異步進行l(wèi)ayer渲染(Facebook開源的異步繪制框架AsyncDisplayKit)

? ? ? ? 4.設置layer的opaque值為YES,減少復雜圖層合成

? ? ? ? 5.盡量使用不包含透明(alpha)通道的圖片資源

? ? ? ? 6. 盡量設置layer的大小值為整形值

? ? ? ? 7.直接讓美工把圖片切成圓角進行顯示,這是效率最高的一種方案

? ? ? ? 8.很多情況下用戶上傳圖片進行顯示,可以讓服務端處理圓角

? ? ? ? 9.使用代碼手動生成圓角Image設置到要顯示的View上,利用UIBezierPath(CoreGraphics框架)畫出來圓角圖片

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容