本文是基于UIKit性能調(diào)優(yōu)實(shí)戰(zhàn)講解 - 簡書這篇文章的總結(jié)詳細(xì)請看原文
實(shí)例講解可以參考:小心別讓圓角成了你列表的幀數(shù)殺手 - CocoaChina_讓移動開發(fā)更簡單
fps表示frames per second岁经,也就是每秒鐘顯示多少幀畫面。對于靜止不變的內(nèi)容冷尉,我們不需要考慮它的刷新率兜挨,但在執(zhí)行動畫或滑動時褐桌,fps的值直接反映出滑動的流暢程度
調(diào)試優(yōu)化:
1.圖層混合:
首先理解像素:像素就是屏幕上的每一個點(diǎn)由RGB三種顏色構(gòu)成有時候會帶有alpha勘究,如果某一個區(qū)域覆蓋了多個layer聘裁,最后的顯示效果就會收到影響艺挪,顯色混合會消耗一定的GPU赴叹,當(dāng)把最上層的顏色透明度設(shè)置為100%時GPU就會忽略下面所有的layer節(jié)省不必要的運(yùn)算
第一個調(diào)試選項(xiàng)"Color Blended Layers"正是用于檢測哪里發(fā)生了圖層混合鸿染,并用紅色標(biāo)記出來。因此我們需要盡可能減少看到的紅色區(qū)域乞巧。一旦發(fā)現(xiàn)應(yīng)該想法設(shè)法消除它
解決方案:a.將控件的opaque = true 原理是希望避免圖層混合涨椒,但是作用不大,因?yàn)閁IView的這個屬性默認(rèn)就是true只要不是人為的設(shè)置為透明都不會出現(xiàn)圖層混合绽媒,但對于UIImageView他本身和圖片都不能為透明的蚕冬,圖片自身的性質(zhì)也可能會對結(jié)果產(chǎn)生影響,比opaque屬性更重要的是backgroundColor屬性是辕,如果不設(shè)置這個屬性囤热,控件依然被認(rèn)為是透明的,所以我們做的是將他設(shè)置為白色
PS:如果label文字有中文获三,依然會出現(xiàn)圖層混合旁蔼,這是因?yàn)榇藭rlabel多了一個sublayer,如果有好的解決辦法歡迎告訴我疙教。
2.光柵化光柵化會導(dǎo)致離屏渲染
光柵化是將一個layer預(yù)先渲染成位圖(bitmap)棺聊,然后加入緩存中。如果對于陰影效果這樣比較消耗資源的靜態(tài)內(nèi)容進(jìn)行緩存贞谓,可以得到一定幅度的性能提升限佩。demo中的這一行代碼表示將label的layer光柵化:
label.layer.shouldRasterize?=?true
Instrument中,第二個調(diào)試選項(xiàng)是“Color Hits Green and Misses Red”裸弦,它表示如果命中緩存則顯示為綠色祟同,否則顯示為紅色,顯然綠色越多越好理疙,紅色越少越好晕城。
光柵化的緩存機(jī)制是一把雙刃劍,先寫入緩存再讀取有可能消耗較多的時間窖贤。因此光柵化僅適用于較復(fù)雜的广辰、靜態(tài)的效果暇矫。通過Instrument的調(diào)試發(fā)現(xiàn)主之,這里使用光柵化經(jīng)常出現(xiàn)未命中緩存的情況择吊,如果沒有特殊需要則可以關(guān)閉光柵化
3.顏色格式:
像素在內(nèi)存中的布局和它在磁盤中的存儲方式并不相同〔坜龋考慮一種簡單的情況:每個像素有R几睛、G、B和alpha四個值粤攒,每個值占用1字節(jié)所森,因此每個像素占用4字節(jié)的內(nèi)存空間。一張1920*1080的照片(iPhone6 Plus的分辨率)一共有2,073,600個像素夯接,因此占用了超過8Mb的內(nèi)存焕济。但是一張同樣分辨率的PNG格式或JPEG格式的圖片一般情況下不會有這么大。這是因?yàn)镴PEG將像素?cái)?shù)據(jù)進(jìn)行了一種非常復(fù)雜且可逆的轉(zhuǎn)化盔几。
比如應(yīng)用中有一些從網(wǎng)絡(luò)下載的圖片晴弃,而GPU恰好不支持這個格式,這就需要CPU預(yù)先進(jìn)行格式轉(zhuǎn)化
第三個選項(xiàng)“Color Copied Images”就用來檢測這種實(shí)時的格式轉(zhuǎn)化逊拍,如果有則會將圖片標(biāo)記為藍(lán)色上鞠。
4.圖片大小
第五個選項(xiàng)“Color Misaligned Images”。它表示如果圖片需要縮放則標(biāo)記為黃色芯丧,如果沒有像素對齊則標(biāo)記為紫色
第三個優(yōu)化是調(diào)整所有圖片的像素大小以避免不必要的縮放芍阎。
5.離屏渲染
正常的渲染通道:OpenGL提交一個命令到Command Buffer,隨后GPU開始渲染缨恒,渲染結(jié)果放到Render Buffer中谴咸,這是正常的渲染流程
有一些復(fù)雜的效果無法直接渲染出結(jié)果,它需要分步渲染最后再組合起來骗露,比如添加一個蒙版(mask):GPU分別得到了紋理(texture岭佳,也就是那個相機(jī)圖標(biāo))和layer(藍(lán)色的蒙版)的渲染結(jié)果。但這兩個渲染結(jié)果沒有直接放入Render Buffer中椒袍,也就表示這是離屏渲染驼唱。直到第三個渲染通道,才把兩者組合起來放入Render Buffer中驹暑。離屏渲染意味著把渲染結(jié)果臨時保存玫恳,等用到時再取出,因此相對于普通渲染更占用資源优俘。
第六個選項(xiàng)“Color Offscreen-Rendered Yellow”會把需要離屏渲染的地方標(biāo)記為黃色京办,大部分情況下我們需要盡可能避免黃色的出現(xiàn)。離屏渲染可能會自動觸發(fā)帆焕,也可以手動觸發(fā)惭婿。以下情況可能會導(dǎo)致觸發(fā)離屏渲染:
重寫drawRect方法
有mask或者是陰影(layer.masksToBounds, layer.shadow*)不恭,模糊效果也是一種mask
layer.shouldRasterize = true
前兩者會自動觸發(fā)離屏渲染,第三種方法是手動開啟離屏渲染财饥。
第四個優(yōu)化换吧,在設(shè)置陰影效果的四行代碼下面添加一行:
imgView.layer.shadowPath = UIBezierPath(rect: imgView.bounds).CGPath
這行代碼制定了陰影路徑,如果沒有手動指定钥星,Core Animation會去自動計(jì)算沾瓦,這就會觸發(fā)離屏渲染。如果人為指定了陰影路徑谦炒,就可以免去計(jì)算贯莺,從而避免產(chǎn)生離屏渲染。
設(shè)置cornerRadius本身并不會導(dǎo)致離屏渲染宁改,但很多時候它還需要配合layer.masksToBounds = true使用缕探。根據(jù)之前的總結(jié),設(shè)置masksToBounds會導(dǎo)致離屏渲染还蹲。解決方案是盡可能在滑動時避免設(shè)置圓角爹耗,如果必須設(shè)置圓角,可以使用光柵化技術(shù)將圓角緩存起來:
//?設(shè)置圓角 ? ?ps:用后用后無效果
label.layer.masksToBounds?=?true
label.layer.cornerRadius?=?8
label.layer.shouldRasterize?=?true
label.layer.rasterizationScale?=?layer.contentsScale
6.快速路徑:
之前將離屏渲染和渲染路徑時的示意圖么秽誊,離屏渲染的最后一步是把此前的多個路徑組合起來鲸沮。如果這個組合過程能由CPU完成,就會大量減少GPU的工作锅论。這種技術(shù)在繪制地圖中可能用到讼溺。
第七個選項(xiàng)“Color Compositing Fast-Path Blue”用于標(biāo)記由硬件繪制的路徑,藍(lán)色越多越好最易。
7.變化區(qū)域
刷新視圖時怒坯,我們應(yīng)該把需要重繪的區(qū)域盡可能縮小。對于未發(fā)生變化的內(nèi)容則不應(yīng)該重繪藻懒,第八個選項(xiàng)“Flash updated Regions”用于標(biāo)記發(fā)生重繪的區(qū)域剔猿。一個典型的例子是系統(tǒng)的時鐘應(yīng)用,絕大多數(shù)時候只有顯示秒針的區(qū)域需要重繪:
總結(jié)
如果你一步一步做到了這里嬉荆,我想一定會有不少收益归敬。不過,學(xué)而不思則罔鄙早,思而不學(xué)則殆汪茧。動手實(shí)踐后還是應(yīng)該總結(jié)提煉,優(yōu)化滑動性能主要涉及三個方面:
避免圖層混合
確毕薹控件的opaque屬性設(shè)置為true舱污,確保backgroundColor和父視圖顏色一致且不透明
如無特殊需要,不要設(shè)置低于1的alpha值
確保UIImage沒有alpha通道
避免臨時轉(zhuǎn)換
確保圖片大小和frame一致弥虐,不要在滑動時縮放圖片
確保圖片顏色格式被GPU支持扩灯,避免勞煩CPU轉(zhuǎn)換
慎用離屏渲染
絕大多數(shù)時候離屏渲染會影響性能
重寫drawRect方法媚赖,設(shè)置圓角、陰影珠插、模糊效果惧磺,光柵化都會導(dǎo)致離屏渲染
設(shè)置陰影效果是加上陰影路徑
滑動時若需要圓角效果,開啟光柵化
實(shí)戰(zhàn)
本文的demo可以在我的Github上下載丧失,然后一步一步自己體驗(yàn)優(yōu)化過程豺妓。但demo畢竟是刻意搭建的一個環(huán)境,我會在我自己的仿寫的簡書app上不斷進(jìn)行實(shí)戰(zhàn)優(yōu)化布讹,歡迎共同學(xué)習(xí)交流。
代碼:
UIView圓角:
#import "UIView+AddCorner.h"
@implementation UIView (AddCorner)
-(double)roundbyuint:(double) num anUint:(double )unit{
double remain = modf(num, &unit);
if (remain > unit / 2.0) {
return [self ceilbyuint:num andUint:unit];
} else {
return [self floorbyuint:num andUint:unit];
}
}
-(double)ceilbyuint:(double)num andUint:(double)unit{
return num - modf(num, &unit) + unit;
//將浮點(diǎn)數(shù)num分解成整數(shù)部分和小數(shù)部分
}
-(double)floorbyuint:(double)num andUint:(double)unit{
return num - modf(num, &unit) ;
}
-(double)piexl:(double)num{
double unit;
switch ((int)[UIScreen mainScreen].scale) {
case 1: unit = 1.0/1.0; break;
case 2: unit = 1.0/2.0; break;
case 3: unit = 1.0/3.0; break;
default: unit = 0.0;
break;
}
return [self roundbyuint:num anUint:unit];
}
-(void)kt_viewAddCorner:(CGFloat)radius andBorderWidth:(CGFloat)borderWidth andBorderColor:(UIColor*)borderColor andBackgroundColor:(UIColor*)backgroundColor{
UIImageView * imageView = [[UIImageView alloc]initWithImage:[self ViewAddCorner:radius andBorderWidth:borderWidth andBorderColor:borderColor andBackgroundColor:backgroundColor]] ;
[self insertSubview:imageView atIndex:0];
}
-(UIImage *)ViewAddCorner:(CGFloat)radius andBorderWidth:(CGFloat)borderWidth andBorderColor:(UIColor*)borderColor andBackgroundColor:(UIColor*)backgroundColor {
CGSize sizeToFit = CGSizeMake((double)CGRectGetWidth(self.frame), (double)CGRectGetHeight(self.frame));
CGFloat halfBorderWidth = (CGFloat)borderWidth / 2.0;
UIGraphicsBeginImageContextWithOptions(sizeToFit, false, [UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, borderWidth);
CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGFloat width = sizeToFit.width, height = sizeToFit.height;
CGContextMoveToPoint(context, width - halfBorderWidth, radius + halfBorderWidth);? // 開始坐標(biāo)右邊開始
CGContextAddArcToPoint(context, width - halfBorderWidth, height - halfBorderWidth, width - radius - halfBorderWidth, height - halfBorderWidth, radius);? // 右下角角度
CGContextAddArcToPoint(context, halfBorderWidth, height - halfBorderWidth, halfBorderWidth, height - radius - halfBorderWidth, radius); // 左下角角度
CGContextAddArcToPoint(context, halfBorderWidth, halfBorderWidth, width - halfBorderWidth, halfBorderWidth, radius); // 左上角
CGContextAddArcToPoint(context, width - halfBorderWidth, halfBorderWidth, width - halfBorderWidth, radius + halfBorderWidth, radius) ;// 右上角
CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);
UIImage? *output = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return output;
}
@end
UIImageView圓角:
#import "UIImageView+AddCorner.h"
@implementation UIImageView (AddCorner)
-(void)kt_ImageViewAddCornerWithRadius:(CGFloat)radius{
// self.image = self.image?:[self.image imageAddCornerWithRadius:radius andSize:self.bounds.size];
//注意第三個選項(xiàng)的設(shè)置
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
//在繪制之前先裁剪出一個圓形
[[UIBezierPath bezierPathWithRoundedRect:self.bounds
cornerRadius:radius] addClip];
//圖片在設(shè)置的圓形里面進(jìn)行繪制
[self.image drawInRect:self.bounds];
//獲取圖片
self.image = UIGraphicsGetImageFromCurrentImageContext();
//結(jié)束繪制
UIGraphicsEndImageContext();
}
@end
UIImage圓角:
#import "UIImage+ImageRoundedCorner.h"
@implementation UIImage (ImageRoundedCorner)
- (UIImage*)imageAddCornerWithRadius:(CGFloat)radius andSize:(CGSize)size{
CGRect rect = CGRectMake(0, 0, size.width, size.height);
UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)];
CGContextAddPath(ctx,path.CGPath);
CGContextClip(ctx);
[self drawInRect:rect];
CGContextDrawPath(ctx, kCGPathFillStroke);
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
@end