iOS最常用的控件-UITableView朽们,基本用法就不多說了框舔,大家應(yīng)該都知道实束,當(dāng)然對(duì)于它的優(yōu)化大家也應(yīng)該都有所了解卵惦。下面我就來談?wù)勥@個(gè)老話題-UITableView的性能優(yōu)化记靡,都是一些平時(shí)總結(jié)的經(jīng)驗(yàn)谈竿,分享給大家。
1摸吠、cell復(fù)用
復(fù)用很簡(jiǎn)單空凸,這或許是所有iOS開發(fā)者最為熟知的一個(gè)優(yōu)化內(nèi)容,如下代碼:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *Identifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
return cell;
}
但是寸痢,這樣重用就完美了嗎呀洲?
我們經(jīng)常在注意cellForRowAtIndexPath:中為每一個(gè)cell綁定數(shù)據(jù),實(shí)際上在調(diào)用cellForRowAtIndexPath:的時(shí)候cell還沒有被顯示出來轿腺,為了提高效率我們應(yīng)該把數(shù)據(jù)綁定的操作放在cell顯示出來后再執(zhí)行两嘴,可以在tableView:willDisplayCell:forRowAtIndexPath:(以后簡(jiǎn)稱willDisplayCell)方法中綁定數(shù)據(jù)。
注意willDisplayCell在cell 在tableview展示之前就會(huì)調(diào)用族壳,此時(shí)cell實(shí)例已經(jīng)生成憔辫,所以不能更改cell的結(jié)構(gòu),只能是改動(dòng)cell上的UI的一些屬性(例如label的內(nèi)容等)仿荆。
2贰您、cell高度的計(jì)算
這邊我們分為兩種cell坏平,一種是定高的cell,另外一種是動(dòng)態(tài)高度的cell锦亦。
(1)定高的cell舶替,應(yīng)該采用如下方式:
self.tableView.rowHeight = 88;
這個(gè)方法指定了所有cell高度都是88的tableview,rowHeight默認(rèn)的值是44杠园,所以一個(gè)空的TableView會(huì)顯示成這個(gè)樣子顾瞪。對(duì)于定高cell,直接采用上面方式給定高度抛蚁,不需要實(shí)現(xiàn)tableView:heightForRowAtIndexPath:以節(jié)省不必要的計(jì)算和開銷陈醒。
(2)動(dòng)態(tài)高度的cell
我們需要實(shí)現(xiàn)它的代理,來給出高度:
-(CGFloat)tableView:(UITableView)tableViewheightForRowAtIndexPath:(NSIndexPath)indexPath{
return xxx
}
這個(gè)代理方法實(shí)現(xiàn)后瞧甩,上面的rowHeight的設(shè)置將會(huì)變成無效钉跷。在這個(gè)方法中,我們需要提高cell高度的計(jì)算效率肚逸,來節(jié)省時(shí)間爷辙。
自從iOS8之后有了self-sizing cell的概念,cell可以自己算出高度朦促,使用self-sizing cell需要滿足以下三個(gè)條件:
(1)使用Autolayout進(jìn)行UI布局約束(要求cell.contentView的四條邊都與內(nèi)部元素有約束關(guān)系)膝晾。
(2)指定TableView的estimatedRowHeight屬性的默認(rèn)值。
(3)指定TableView的rowHeight屬性為UITableViewAutomaticDimension思灰。
- (void)viewDidload {
self.myTableView.estimatedRowHeight = 44.0;
self.myTableView.rowHeight = UITableViewAutomaticDimension;
}
除了提高cell高度的計(jì)算效率之外玷犹,對(duì)于已經(jīng)計(jì)算出的高度,我們需要進(jìn)行緩存洒疚,對(duì)于已經(jīng)計(jì)算過的高度,沒有必要進(jìn)行計(jì)算第二次坯屿。
3油湖、渲染
為了保證TableView的流暢,當(dāng)快速滑動(dòng)的時(shí)候领跛,cell必須被快速的渲染出來乏德。所以cell渲染的速度必須快。如何提高cell的渲染速度呢吠昭?
(1)當(dāng)有圖像時(shí)喊括,預(yù)渲染圖像,在bitmap context先將其畫一遍矢棚,導(dǎo)出成UIImage對(duì)象郑什,然后再繪制到屏幕,這會(huì)大大提高渲染速度蒲肋。具體內(nèi)容可以自行查找“利用預(yù)渲染加速顯示iOS圖像”相關(guān)資料蘑拯。
(2)渲染最好時(shí)的操作之一就是混合(blending)了,所以我們不要使用透明背景钝满,將cell的opaque值設(shè)為Yes,背景色不要使用clearColor申窘,盡量不要使用陰影漸變等
(3)由于混合操作是使用GPU來執(zhí)行弯蚜,我們可以用CPU來渲染,這樣混合操作就不再執(zhí)行剃法∷檗啵可以在UIView的drawRect方法中自定義繪制。
4贷洲、減少視圖的數(shù)目
我們?cè)赾ell上添加系統(tǒng)控件的時(shí)候收厨,實(shí)際上系統(tǒng)都會(huì)調(diào)用底層的接口進(jìn)行繪制,大量添加控件時(shí)恩脂,會(huì)消耗很大的資源并且也會(huì)影響渲染的性能帽氓。當(dāng)使用默認(rèn)的UITableViewCell并且在它的ContentView上面添加控件時(shí)會(huì)相當(dāng)消耗性能。所以目前最佳的方法還是繼承UITableViewCell俩块,并重寫drawRect方法黎休。
5、減少多余的繪制操作
在實(shí)現(xiàn)drawRect方法的時(shí)候玉凯,它的參數(shù)rect就是我們需要繪制的區(qū)域势腮,在rect范圍之外的區(qū)域我們不需要進(jìn)行繪制,否則會(huì)消耗相當(dāng)大的資源漫仆。
6捎拯、不要給cell動(dòng)態(tài)添加subView
在初始化cell的時(shí)候就將所有需要展示的添加完畢,然后根據(jù)需要來設(shè)置hide屬性顯示和隱藏盲厌。
7署照、異步化UI,不要阻塞主線程
我們時(shí)常會(huì)看到這樣一個(gè)現(xiàn)象吗浩,就是加載時(shí)整個(gè)頁面卡住不動(dòng)建芙,怎么點(diǎn)都沒用,仿佛死機(jī)了一般懂扼。原因是主線程被阻塞了禁荸。所以對(duì)于網(wǎng)路數(shù)據(jù)的請(qǐng)求或者圖片的加載,我們可以開啟多線程阀湿,將耗時(shí)操作放到子線程中進(jìn)行赶熟,異步化操作。這個(gè)或許每個(gè)iOS開發(fā)者都知道的知識(shí)陷嘴,不必多講映砖。
8、滑動(dòng)時(shí)按需加載對(duì)應(yīng)的內(nèi)容
如果目標(biāo)行與當(dāng)前行相差超過指定行數(shù)罩旋,只在目標(biāo)滾動(dòng)范圍的前后指定3行加載啊央。
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollViewwithVelocity:(CGPoint)velocitytargetContentOffset:(inoutCGPoint *)targetContentOffset{
NSIndexPath *ip=[selfindexPathForRowAtPoint:CGPointMake(0,targetContentOffset->y)];
NSIndexPath *cip=[[selfindexPathsForVisibleRows]firstObject];
NSIntegerskipCount=8;
if(labs(cip.row-ip.row)>skipCount){
NSArray *temp=[selfindexPathsForRowsInRect:CGRectMake(0,targetContentOffset->y,self.width,self.height)];
NSMutableArray *arr=[NSMutableArrayarrayWithArray:temp];
if(velocity.y<0){
NSIndexPath**indexPath=[templastObject];
if(indexPath.row+33){
[arraddObject:[NSIndexPathindexPathForRow:indexPath.row-3inSection:0]];
[arraddObject:[NSIndexPathindexPathForRow:indexPath.row-2inSection:0]];
[arraddObject:[NSIndexPathindexPathForRow:indexPath.row-1inSection:0]];
}
}
[needLoadArraddObjectsFromArray:arr];
}
}
記得在tableView:cellForRowAtIndexPath:方法中加入判斷:
if(needLoadArr.count>0&&[needLoadArrindexOfObject:indexPath]==NSNotFound){
[cellclear];
return;
}
記得在tableView:cellForRowAtIndexPath:方法中加入判斷:
if(needLoadArr.count>0&&[needLoadArrindexOfObject:indexPath]==NSNotFound){
[cellclear];
return;
}
滑動(dòng)很快時(shí)眶诈,只加載目標(biāo)范圍內(nèi)的cell,這樣按需加載(配合SDWebImage)瓜饥,極大提高流暢度逝撬。
9、最后想談下離屏渲染的問題:
9.1乓土、下面的情況或操作會(huì)引發(fā)離屏渲染:
為圖層設(shè)置遮罩(layer.mask)
將圖層的layer.masksToBounds / view.clipsToBounds屬性設(shè)置為true
將圖層layer.allowsGroupOpacity屬性設(shè)置為YES和layer.opacity小于1.0
為圖層設(shè)置陰影(layer.shadow *)宪潮。
為圖層設(shè)置layer.shouldRasterize=true
具有l(wèi)ayer.cornerRadius,layer.edgeAntialiasingMask趣苏,layer.allowsEdgeAntialiasing的圖層
文本(任何種類狡相,包括UILabel,CATextLayer食磕,Core Text等)尽棕。
使用CGContext在drawRect :方法中繪制大部分情況下會(huì)導(dǎo)致離屏渲染,甚至僅僅是一個(gè)空的實(shí)現(xiàn)
9.2彬伦、優(yōu)化方案
官方對(duì)離屏渲染產(chǎn)生性能問題也進(jìn)行了優(yōu)化:
iOS 9.0 之前UIimageView跟UIButton設(shè)置圓角都會(huì)觸發(fā)離屏渲染滔悉。
iOS 9.0 之后UIButton設(shè)置圓角會(huì)觸發(fā)離屏渲染,而UIImageView里png圖片設(shè)置圓角不會(huì)觸發(fā)離屏渲染了单绑,如果設(shè)置其他陰影效果之類的還是會(huì)觸發(fā)離屏渲染的回官。
(1)圓角優(yōu)化
在APP開發(fā)中,圓角圖片還是經(jīng)常出現(xiàn)的搂橙。如果一個(gè)界面中只有少量圓角圖片或許對(duì)性能沒有非常大的影響歉提,但是當(dāng)圓角圖片比較多的時(shí)候就會(huì)APP性能產(chǎn)生明顯的影響。
我們?cè)O(shè)置圓角一般通過如下方式:
imageView.layer.cornerRadius=CGFloat(10);
imageView.layer.masksToBounds=YES;
這樣處理的渲染機(jī)制是GPU在當(dāng)前屏幕緩沖區(qū)外新開辟一個(gè)渲染緩沖區(qū)進(jìn)行工作区转,也就是離屏渲染苔巨,這會(huì)給我們帶來額外的性能損耗,如果這樣的圓角操作達(dá)到一定數(shù)量废离,會(huì)觸發(fā)緩沖區(qū)的頻繁合并和上下文的的頻繁切換恋拷,性能的代價(jià)會(huì)宏觀地表現(xiàn)在用戶體驗(yàn)上——掉幀。
優(yōu)化方案1:使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個(gè)圓角
UIImageView imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
imageView.image = [UIImage imageNamed:@"myImg"];
//開始對(duì)imageView進(jìn)行畫圖
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
//使用貝塞爾曲線畫出一個(gè)圓形圖
[[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=[[UIImageView alloc]initWithFrame:CGRectMake(100,100,100,100)];
imageView.image=[UIImage imageNamed:@"myImg"];
UIBezierPath *maskPath=[UIBezierPath bezierPathWithRoundedRect:imageView.boundsbyRoundingCorners:UIRectCornerAllCornerscornerRadii:imageView.bounds.size];
CAShapeLayer *maskLayer=[[CAShapeLayer alloc]init];
//設(shè)置大小
maskLayer.frame=imageView.bounds;
//設(shè)置圖形樣子
maskLayer.path=maskPath.CGPath;
imageView.layer.mask=maskLayer;
[self.view addSubview:imageView];
對(duì)于方案2需要解釋的是:
CAShapeLayer繼承于CALayer,可以使用CALayer的所有屬性值厅缺;
CAShapeLayer需要貝塞爾曲線配合使用才有意義(也就是說才有效果)
使用CAShapeLayer(屬于CoreAnimation)與貝塞爾曲線可以實(shí)現(xiàn)不在view的drawRect(繼承于CoreGraphics走的是CPU,消耗的性能較大)方法中畫出一些想要的圖形
CAShapeLayer動(dòng)畫渲染直接提交到手機(jī)的GPU當(dāng)中,相較于view的drawRect方法使用CPU渲染而言宴偿,其效率極高湘捎,能大大優(yōu)化內(nèi)存使用情況。
總的來說就是用CAShapeLayer的內(nèi)存消耗少窄刘,渲染速度快窥妇,建議使用優(yōu)化方案2。
(2)shadow優(yōu)化對(duì)于shadow娩践,如果圖層是個(gè)簡(jiǎn)單的幾何圖形或者圓角圖形活翩,我們可以通過設(shè)置shadowPath來優(yōu)化性能烹骨,能大幅提高性能。示例如下:
imageView.layer.shadowColor=[UIColor grayColor].CGColor;
imageView.layer.shadowOpacity=1.0;
imageView.layer.shadowRadius=2.0;
UIBezierPath *path=[UIBezierPath bezierPathWithRect:imageView.frame];
imageView.layer.shadowPath=path.CGPath;
我們還可以通過設(shè)置shouldRasterize屬性值為YES來強(qiáng)制開啟離屏渲染材泄。其實(shí)就是光柵化(Rasterization)沮焕。既然離屏渲染這么不好,為什么我們還要強(qiáng)制開啟呢拉宗?當(dāng)一個(gè)圖像混合了多個(gè)圖層峦树,每次移動(dòng)時(shí),每一幀都要重新合成這些圖層旦事,十分消耗性能魁巩。當(dāng)我們開啟光柵化后,會(huì)在首次產(chǎn)生一個(gè)位圖緩存姐浮,當(dāng)再次使用時(shí)候就會(huì)復(fù)用這個(gè)緩存谷遂。但是如果圖層發(fā)生改變的時(shí)候就會(huì)重新產(chǎn)生位圖緩存。所以這個(gè)功能一般不能用于UITableViewCell中卖鲤,cell的復(fù)用反而降低了性能肾扰。最好用于圖層較多的靜態(tài)內(nèi)容的圖形。而且產(chǎn)生的位圖緩存的大小是有限制的扫尖,一般是2.5個(gè)屏幕尺寸白对。在100ms之內(nèi)不使用這個(gè)緩存,緩存也會(huì)被刪除换怖。所以我們要根據(jù)使用場(chǎng)景而定甩恼。
(3)其他的一些優(yōu)化建議
當(dāng)我們需要圓角效果時(shí),可以使用一張中間透明圖片蒙上去
使用ShadowPath指定layer陰影效果路徑
使用異步進(jìn)行l(wèi)ayer渲染(Facebook開源的異步繪制框架AsyncDisplayKit)
設(shè)置layer的opaque值為YES沉颂,減少?gòu)?fù)雜圖層合成
盡量使用不包含透明(alpha)通道的圖片資源
盡量設(shè)置layer的大小值為整形值
直接讓美工把圖片切成圓角進(jìn)行顯示条摸,這是效率最高的一種方案
很多情況下用戶上傳圖片進(jìn)行顯示,可以讓服務(wù)端處理圓角
使用代碼手動(dòng)生成圓角Image設(shè)置到要顯示的View上铸屉,利用UIBezierPath(CoreGraphics框架)畫出來圓角圖片
(4)Core Animation工具檢測(cè)離屏渲染
對(duì)于離屏渲染的檢測(cè)钉蒲,蘋果為我們提供了一個(gè)測(cè)試工具Core Animation〕固常可以在Xcode->Open Develeper Tools->Instruments中找到顷啼,如下圖:
Core Animation工具用來監(jiān)測(cè)Core Animation性能,提供可見的FPS值昌屉,并且提供幾個(gè)選項(xiàng)來測(cè)量渲染性能钙蒙。如下圖:
下面我們來說明每個(gè)選項(xiàng)的功能:
Color Blended Layers:這個(gè)選項(xiàng)如果勾選,你能看到哪個(gè)layer是透明的间驮,GPU正在做混合計(jì)算躬厌。顯示紅色的就是透明的,綠色就是不透明的竞帽。
Color Hits Green and Misses Red:如果勾選這個(gè)選項(xiàng)扛施,且當(dāng)我們代碼中有設(shè)置shouldRasterize為YES鸿捧,那么紅色代表沒有復(fù)用離屏渲染的緩存,綠色則表示復(fù)用了緩存疙渣。我們當(dāng)然希望能夠復(fù)用匙奴。
Color Copied Images:按照官方的說法,當(dāng)圖片的顏色格式GPU不支持的時(shí)候昌阿,Core Animation會(huì)
拷貝一份數(shù)據(jù)讓CPU進(jìn)行轉(zhuǎn)化饥脑。例如從網(wǎng)絡(luò)上下載了TIFF格式的圖片,則需要CPU進(jìn)行轉(zhuǎn)化懦冰,這個(gè)區(qū)域會(huì)顯示成藍(lán)色灶轰。還有一種情況會(huì)觸發(fā)Core Animation的copy方法,就是字節(jié)不對(duì)齊的時(shí)候刷钢。如下圖:
Color Immediately:默認(rèn)情況下Core Animation工具以每毫秒10次的頻率更新圖層調(diào)試顏色笋颤,如果勾選這個(gè)選項(xiàng)則移除10ms的延遲。對(duì)某些情況需要這樣内地,但是有可能影響正常幀數(shù)的測(cè)試伴澄。
Color Misaligned Images:勾選此項(xiàng),如果圖片需要縮放則標(biāo)記為黃色阱缓,如果沒有像素對(duì)齊則標(biāo)記為紫色非凌。像素對(duì)齊我們已經(jīng)在上面有所介紹。
Color Offscreen-Rendered Yellow:用來檢測(cè)離屏渲染的荆针,如果顯示黃色敞嗡,表示有離屏渲染。當(dāng)然還要結(jié)合Color Hits Green and Misses Red來看航背,是否復(fù)用了緩存喉悴。
Color OpenGL Fast Path Blue:這個(gè)選項(xiàng)對(duì)那些使用OpenGL的圖層才有用,像是GLKView或者 CAEAGLLayer玖媚,如果不顯示藍(lán)色則表示使用了CPU渲染箕肃,繪制在了屏幕外,顯示藍(lán)色表示正常今魔。
Flash Updated Regions:當(dāng)對(duì)圖層重繪的時(shí)候回顯示黃色勺像,如果頻繁發(fā)生則會(huì)影響性能〈砩可以用增加緩存來增強(qiáng)性能咏删。
以上就是本人的一些總結(jié),當(dāng)然對(duì)于UITableView的性能優(yōu)化问词,網(wǎng)上有很多相關(guān)的資料。如果有什么不同的觀點(diǎn)嘀粱,歡迎大家補(bǔ)充激挪。