前言
????????說起優(yōu)化,簡直是博大精深诽凌。話不多說矾瑰,筆者今天梳理的內(nèi)容,UITableView的性能優(yōu)化酵镜。先說一下tableview的執(zhí)行順序:
1.它會調(diào)用代理方法確定有幾個分區(qū)
numberOfSectionsInTableView:
2.確定每個分區(qū)的表頭高和表尾高(如果設(shè)定了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的復(fù)用
? ? ????首先cell有兩種復(fù)用方法:
????????????????- (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可復(fù)用的時候會自動創(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還沒有被顯示出來嗅虏,為了提高效率我們應(yīng)該把數(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,應(yīng)該采用如下方式:
????????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的設(shè)置將會變成無效。在這個方法中矾踱,我們需要提高cell高度的計算效率恨狈,來節(jié)省時間。自從iOS8之后有了Self-Sizing cell的概念呛讲,cell可以自己算出高度禾怠,IOS11以后返奉,Self-Sizing默認開啟,包括Headers, footers吗氏。
????????使用self-sizing cell需要滿足以下三個條件:
????????(1)使用Autolayout進行UI布局約束(要求cell.contentView的四條邊都與內(nèi)部元素有約束關(guān)系)芽偏。
????????(2)指定TableView的estimatedRowHeight屬性的默認值。
????????(3)指定TableView的rowHeight屬性為UITableViewAutomaticDimension弦讽。
????????- (void)viewDidload {
????????????self.myTableView.estimatedRowHeight = 44.0;
????????????self.myTableView.rowHeight = UITableViewAutomaticDimension;
????????}
三污尉、cell的渲染
????????為了保證TableView的流暢,當快速滑動的時候往产,cell必須被快速的渲染出來被碗。所以cell渲染的速度必須快。如何提高cell的渲染速度呢仿村?
????????(1)當有圖像時锐朴,預(yù)渲染圖像,在bitmap context先將其畫一遍蔼囊,導(dǎo)出成UIImage對象焚志,然后再繪制到屏幕,這會大大提高渲染速度畏鼓。具體內(nèi)容可以自行查找“利用預(yù)渲染加速顯示iOS圖像”相關(guān)資料
????????(2)渲染最好時的操作之一就是混合(blending)了,所以我們不要使用透明背景娩嚼,將cell的opaque值設(shè)為Yes,背景色不要使用clearColor滴肿,盡量不要使用陰影漸變等岳悟;
????????(3)由于混合操作是使用GPU來執(zhí)行,我們可以用CPU來渲染泼差,這樣混合操作就不再執(zhí)行贵少。可以在UIView的drawRect方法中自定義繪制堆缘;
????????(4)減少subviews的個數(shù)和層級滔灶。子控件的層級越深,渲染到屏幕上所需要的計算量就越大吼肥;如多用drawRect繪制元素录平,替代用view顯示;
????????(5)少用subviews的透明圖層缀皱。對于不透明的View斗这,設(shè)置opaque為YES,這樣在繪制該View時啤斗,就不需要考慮被View覆蓋的其他內(nèi)容(盡量設(shè)置Cell的view為opaque表箭,避免GPU對Cell下面的內(nèi)容也進行繪制);
?????????(6)避免CALayer特效(shadowPath)钮莲。 給Cell中View加陰影會引起性能問題免钻,如下面代碼會導(dǎo)致滾動時有明顯的卡頓:
????????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ù)需要來設(shè)置hide屬性顯示和隱藏。
????????(10)異步化UI步鉴,不要阻塞主線程.
? ? ? ? (11)滑動時按需加載對應(yīng)的內(nèi)容
? ? ? ? 參考文章
四揪胃、離屏渲染
????????下面的情況或操作會引發(fā)離屏渲染:
? ? ? ? (1)、為圖層設(shè)置遮罩(layer.mask)氛琢;
? ? ? ? (2)喊递、將圖層的layer.masksToBounds / view.clipsToBounds屬性設(shè)置為true;
? ? ? ? (3)阳似、將圖層layer.allowsGroupOpacity屬性設(shè)置為YES和layer.opacity小于1.0骚勘;
? ? ? ? (4)、為圖層設(shè)置陰影(layer.shadow *)撮奏;
? ? ? ? (5)俏讹、為圖層設(shè)置layer.shouldRasterize=true;
? ? ? ? (6)畜吊、具有l(wèi)ayer.cornerRadius泽疆,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的圖層玲献;
? ? ? ? (7)殉疼、文本(任何種類,包括UILabel捌年,CATextLayer瓢娜,Core Text等);
? ? ? ? (8)礼预、使用CGContext在drawRect :方法中繪制大部分情況下會導(dǎo)致離屏渲染恋腕,甚至僅僅是一個空的實現(xiàn)。
1逆瑞、優(yōu)化方案
????????官方對離屏渲染產(chǎn)生性能問題也進行了優(yōu)化:
????????iOS 9.0 之前UIimageView跟UIButton設(shè)置圓角都會觸發(fā)離屏渲染荠藤。iOS 9.0 之后UIButton設(shè)置圓角會觸發(fā)離屏渲染伙单,而UIImageView里png圖片設(shè)置圓角不會觸發(fā)離屏渲染了,如果設(shè)置其他陰影效果之類的還是會觸發(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設(shè)置圓角
????????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];
????????//設(shè)置大小
????????maskLayer.frame=imageView.bounds;
????????//設(shè)置圖形樣子
????????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)存使用情況”液荩總的來說就是用CAShapeLayer的內(nèi)存消耗少游两,渲染速度快,建議使用優(yōu)化方案2漩绵。
(2)shadow優(yōu)化
????????對于shadow贱案,如果圖層是個簡單的幾何圖形或者圓角圖形,我們可以通過設(shè)置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;
????????我們還可以通過設(shè)置shouldRasterize屬性值為YES來強制開啟離屏渲染。其實就是光柵化(Rasterization)碍扔。既然離屏渲染這么不好瘩燥,為什么我們還要強制開啟呢?當一個圖像混合了多個圖層不同,每次移動時厉膀,每一幀都要重新合成這些圖層,十分消耗性能二拐。當我們開啟光柵化后站蝠,會在首次產(chǎn)生一個位圖緩存,當再次使用時候就會復(fù)用這個緩存卓鹿。但是如果圖層發(fā)生改變的時候就會重新產(chǎn)生位圖緩存菱魔。所以這個功能一般不能用于UITableViewCell中,cell的復(fù)用反而降低了性能吟孙。最好用于圖層較多的靜態(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.設(shè)置layer的opaque值為YES,減少復(fù)雜圖層合成
? ? ? ? 5.盡量使用不包含透明(alpha)通道的圖片資源
? ? ? ? 6. 盡量設(shè)置layer的大小值為整形值
? ? ? ? 7.直接讓美工把圖片切成圓角進行顯示,這是效率最高的一種方案
? ? ? ? 8.很多情況下用戶上傳圖片進行顯示雏节,可以讓服務(wù)端處理圓角
? ? ? ? 9.使用代碼手動生成圓角Image設(shè)置到要顯示的View上胜嗓,利用UIBezierPath(CoreGraphics框架)畫出來圓角圖片