目錄
- 離屏渲染的本質(zhì)
- 如何設(shè)置圓角(三種方法)
- Shadow 陰影
- Mask
- GroupOpacity
- EdgeAntialiasing
- Rasterization
- 造成離屏渲染的原因
- 總結(jié)
離屏渲染(Offscreen Render)
繪制像素到屏幕上 應(yīng)該是國內(nèi)對離屏渲染這個概念推廣力度最大的一篇文章了赠涮。文章里提到直接將圖層合成到幀的緩沖區(qū)中(在屏幕上)比先創(chuàng)建屏幕外緩沖區(qū)芒炼,然后渲染到紋理中阶祭,最后將結(jié)果渲染到幀的緩沖區(qū)中要廉價很多阶淘。因?yàn)檫@其中涉及兩次昂貴的環(huán)境轉(zhuǎn)換(轉(zhuǎn)換環(huán)境到屏幕外緩沖區(qū)鼓黔,然后轉(zhuǎn)換環(huán)境到幀緩沖區(qū))页畦。觸發(fā)離屏渲染后這種轉(zhuǎn)換發(fā)生在每一幀糊余,在界面的滾動過程中如果有大量的離屏渲染發(fā)生時會嚴(yán)重影響幀率霍殴。
蘋果官方公開的的資料里關(guān)于離屏渲染的信息最早是在 2011年的 WWDC, 在多個 session 里都提到了盡量避免會觸發(fā)離屏渲染的效果帝簇,包括:mask
, shadow
, group opacity
, edge antialiasing
徘郭。
使用 Core Graphics 里的繪制 API 也會觸發(fā)離屏渲染,比如重寫 drawRect:丧肴。在 WWDC 2011: Understanding UIKit Rendering 這個 session 里演示了Core Animation Instruments
里使用Color Offscreen-Renderd Yellow
選項(xiàng)來檢測離屏渲染
残揉。
Designing for iOS: Graphics & Performance這篇文章也提到了使用 Core Graphics API 會觸發(fā)離屏渲染,隨后蘋果對這個觀點(diǎn)進(jìn)行了回復(fù)芋浮,主要意思是:Core Graphics 的繪制 API 的確會觸發(fā)離屏渲染冲甘,但不是那種 GPU 的離屏渲染。使用 Core Graphics 繪制 API 是在 CPU 上執(zhí)行途样,觸發(fā)的是 CPU 版本的離屏渲染。
本文以Color Offscreen-Renderd Yellow
為觸發(fā)離屏渲染的標(biāo)準(zhǔn)濒憋,除非還有這個標(biāo)準(zhǔn)無法檢測出來的引發(fā)離屏渲染的行為何暇。那么 Core Graphics API 是不會觸發(fā)離屏渲染的,比如重寫drawRect:凛驮,而除了以上四種效果會觸發(fā)離屏渲染裆站,使用系統(tǒng)提供的圓角效果也會觸發(fā)離屏渲染,比如這樣:
view.layer.cornerRadius = 5
view.layer.masksToBounds = true
開始之前黔夭,先鋪墊一點(diǎn)基礎(chǔ)的東西宏胯。
UIView 和 CALayer 的關(guān)系
The Relationship Between Layers and Views的解釋很細(xì)致但是太啰嗦,簡單來說本姥,UIView 是對 CALayer 的一個封裝肩袍。
CALayer 負(fù)責(zé)顯示內(nèi)容contents,UIView 為其提供內(nèi)容婚惫,以及負(fù)責(zé)處理觸摸等事件氛赐,參與響應(yīng)鏈。CALayer 的結(jié)構(gòu)如下.出自 Layers Have Their Own Background and Border:
CALayer 有三個視覺元素先舷,中間的contents屬性是這樣聲明的
var contents: AnyObject?
實(shí)際上它必須是一個CGImage才能顯示艰管。
當(dāng)使用let view = UIView(frame: CGRectMake(0, 0, 200, 200))生成一個視圖對象并添加到屏幕上時,從 CALayer 的結(jié)構(gòu)可以知道蒋川,這個視圖的 layer 的三個視覺元素是這樣的:contents為空牲芋,背景顏色為空(透明色),前景框?qū)挾葹?的前景框,這個視圖從視覺上看什么都看不到缸浦。CALayer 文檔第一句話就是:The CALayer class manages image-based content and allows you to perform animations on that content.
UIView 的顯示內(nèi)容很大程度上就是一張圖片(CGImage)
夕冲。
UIImageView
既然直接對 CALayer 的contents
屬性賦值一個CGImage
便能顯示圖片,所以 UIImageView 就順利成章地誕生了餐济。實(shí)際上 UIImage 就是對 CGImage(或者 CIImage) 的一個輕量封裝耘擂。記得我剛接觸 iOS 時,搞不懂這兩者的區(qū)別絮姆,有人這樣對我說過醉冤,沒想到出處是這里:
UIKit 和 Core Graphics 框架的聯(lián)系很緊密,UIKit 里帶CG前綴屬性的類基本上是對應(yīng) Core Graphics 框架里的對象的封裝篙悯,UIKit 里的繪制功能也是 Core Graphics 繪制 API 的封裝蚁阳。Drawing with Quartz and UIKit列舉了這些對應(yīng)關(guān)系。界面的內(nèi)容主要是圖像和文字鸽照,文字是怎么顯示的螺捐?也是使用 Core Graphics 框架繪制出來的。
接下來矮燎,正式開始本文的話題定血。
一 設(shè)置圓角
RoundedCorner
設(shè)置圓角:
view.layer.cornerRadius = 5
文檔中cornerRadius屬性的說明:
Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to YES causes the content to be clipped to the rounded corners.
很明了,只對前景框和背景色起作用诞外,再看 CALayer 的結(jié)構(gòu)澜沟,如果contents有內(nèi)容或者內(nèi)容的背景不是透明的話,還需要把這部分弄個角出來峡谊,不然合成的結(jié)果還是沒有圓角茫虽,所以才要修改masksToBounds為true(在 UIView 上對應(yīng)的屬性是clipsToBounds,在 IB 里對應(yīng)的設(shè)置是「Clip Subiews」選項(xiàng))既们。前些日子很熱鬧的圓角優(yōu)化文章中的2篇指出是修改masksToBounds為true而非修改cornerRadius才是觸發(fā)離屏渲染的原因濒析,但如果以「Color Offscreen-Renderd Yellow」的特征為標(biāo)準(zhǔn)的話,
這兩個屬性單獨(dú)作用時都不是引發(fā)離屏渲染的原因啥纸,他倆合體(masksToBounds = true, cornerRadius>0)才是号杏。
方案1 重繪
重繪的方式有多種,都是殊途同歸斯棒。實(shí)際中重繪圓角的優(yōu)化方案需要考慮的是馒索,將圖像重新繪制為為圓角圖像相當(dāng)于多了一份拷貝,要不要緩存名船?A.第一次重繪后將這些圓角圖像緩存在磁盤里绰上,第二次加載直接使用緩存的圓角圖像;B.直接保存在內(nèi)存里渠驼,在內(nèi)存比較吃緊時顯然不是個好選擇蜈块;C.不緩存,和系統(tǒng)圓角一樣,每次都重繪百揭,浪費(fèi)電量爽哎。
方案2
如果不需要對外部來源的圖片做圓角,由設(shè)計師直接畫成圓角圖片是最方便的器一;
方案3 混合圖層
在要添加圓角的視圖上再疊加一個部分透明的視圖课锌,只對圓角部分進(jìn)行遮擋。VVebo微博客戶端就是這樣做的祈秕,遮擋的部分背景最好與周圍背景相同渺贤。多一個圖層會增加合成的工作量,但這點(diǎn)工作量與離屏渲染相比微不足道请毛,性能上無論各方面都和無效果持平志鞍。下面左側(cè)的圖像是 VVebo 里用來制造圓形頭像的 mask 圖像,實(shí)際中有這種需求的基本是制造圓形頭像方仿,普通的圓角遮罩需要左二這種固棚,左三是通用型。如果疊加的視圖都一樣仙蚜,可以只加載一次遮罩圖片以減少內(nèi)存占用此洲。
- 實(shí)例代碼如下 (上面覆蓋一個圓形遮蓋視圖)
- (void)drawRoundedCornerImage {
UIImageView *iconImgV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
iconImgV.image = [UIImage imageNamed:@"icon"];
[self.view addSubview:iconImgV];
[iconImgV mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(iconImgV.size);
make.top.equalTo(self.view.mas_top).offset(500);
make.centerX.equalTo(self.view);
}];
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
[self.view addSubview:imgView];
[imgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(imgView.size);
make.top.equalTo(iconImgV.mas_top);
make.leading.equalTo(iconImgV.mas_leading);
}];
// imgView.image = [self useUIGraphicsDrawAntiRoundedCornerImageWithRadius:10 outerSize:CGSizeMake(150, 150) innerSize:CGSizeMake(100, 100) fillColor:[UIColor whiteColor]];
// 圓形
imgView.image = [self drawCircleRadius:100 outerSize:CGSizeMake(200, 200) fillColor:[UIColor whiteColor]];
}
// 繪制圓形
- (UIImage *)drawCircleRadius:(float)radius outerSize:(CGSize)outerSize fillColor:(UIColor *)fillColor {
UIGraphicsBeginImageContextWithOptions(outerSize, false, [UIScreen mainScreen].scale);
// 1、獲取當(dāng)前上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
//2.描述路徑
// ArcCenter:中心點(diǎn) radius:半徑 startAngle起始角度 endAngle結(jié)束角度 clockwise:是否逆時針
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(outerSize.width * 0.5, outerSize.height * 0.5) radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:NO];
[bezierPath closePath];
// 3.外邊
[bezierPath moveToPoint:CGPointMake(0, 0)];
[bezierPath addLineToPoint:CGPointMake(outerSize.width, 0)];
[bezierPath addLineToPoint:CGPointMake(outerSize.width, outerSize.height)];
[bezierPath addLineToPoint:CGPointMake(0, outerSize.height)];
[bezierPath addLineToPoint:CGPointMake(0, 0)];
[bezierPath closePath];
//4.設(shè)置顏色
[fillColor setFill];
[bezierPath fill];
CGContextDrawPath(contextRef, kCGPathStroke);
UIImage *antiRoundedCornerImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return antiRoundedCornerImage;
}
效果如下
布局如下圖所示
如何在文本視圖類上實(shí)現(xiàn)圓角
文本視圖主要是這三類:UILabel, UITextField, UITextView委粉。其中 UITextField 類自帶圓角風(fēng)格的外型呜师,UILabel 和 UITextView 要想顯示圓角需要表現(xiàn)出與周圍不同的背景色才行。想要在 UILabel 和 UITextView 上實(shí)現(xiàn)低成本的圓角(不觸發(fā)離屏渲染)艳丛,需要保證 layer 的contents呈現(xiàn)透明的背景色,文本視圖類的 layer 的contents默認(rèn)是透明的(字符就在這個透明的環(huán)境里繪制趟紊、顯示)氮双,此時只需要設(shè)置 layer 的backgroundColor,再加上cornerRadius就可以搞定了霎匈。不過 UILabel 上設(shè)置backgroundColor的行為被更改了戴差,不再是設(shè)定 layer 的背景色而是為contents設(shè)置背景色,UITextView 則沒有改變這一點(diǎn)铛嘱,實(shí)例代碼如下
- UILabel
- (void)drawUI {
UILabel *radiusLbe = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 44)];
radiusLbe.textColor = [UIColor blackColor];
radiusLbe.text = @"裁剪圓角";
radiusLbe.textAlignment = NSTextAlignmentCenter;
// a裁剪圓角
radiusLbe.layer.backgroundColor = [[UIColor orangeColor] CGColor];
radiusLbe.layer.cornerRadius = 10;
[self.view addSubview:radiusLbe];
}
效果如下
- UITextField
- (void)drawTextF {
UITextField *textF = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 200, 44)];
textF.textColor = [UIColor blackColor];
textF.text = @"裁剪圓角";
textF.textAlignment = NSTextAlignmentCenter;
// a裁剪圓角
textF.layer.backgroundColor = [[UIColor greenColor] CGColor];
textF.layer.cornerRadius = 10;
[self.view addSubview:textF];
}
運(yùn)行效果如下
- UITextView
- (void)drawTextV {
UITextView *textV = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 20, 200)];
textV.textColor = [UIColor whiteColor];
textV.text = @"如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n";
textV.textAlignment = NSTextAlignmentCenter;
// a裁剪圓角
textV.layer.backgroundColor = [[UIColor blueColor] CGColor];
textV.layer.cornerRadius = 10;
[self.view addSubview:textV];
}
運(yùn)行效果如下:
二 Shadow 陰影
陰影直接合成在視圖的下面暖释,視圖結(jié)構(gòu)里并沒有多出一個視圖。在沒有指定陰影路徑時墨吓,陰影是沿著視圖的非透明部分?jǐn)U展的球匕,而且 CALayer 的三個視覺元素至少有一個存在時才會有陰影。
使用陰影必須保證 layer
的masksToBounds = false
帖烘,因此陰影與系統(tǒng)圓角不兼容
亮曹。但是注意,只是在視覺上看不到,對性能的影響依然照卦。通常這樣實(shí)現(xiàn)一個陰影:
- (void)drawUI {
UIImageView *iconImgV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
iconImgV.image = [UIImage imageNamed:@"icon_girl"];
[self.view addSubview:iconImgV];
[iconImgV mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(iconImgV.size);
make.centerY.equalTo(self.view);
make.centerX.equalTo(self.view);
}];
// 設(shè)置陰影
CALayer *imageViewLayer = iconImgV.layer;
imageViewLayer.shadowColor = [[UIColor blackColor] CGColor];
imageViewLayer.shadowOpacity = 1.0; //此參數(shù)默認(rèn)為0式矫,即陰影不顯示
imageViewLayer.shadowRadius = 2.0; //給陰影加上圓角,對性能無明顯影響
imageViewLayer.shadowOffset = CGSizeMake(5, 5);
//設(shè)定路徑:與視圖的邊界相同
UIBezierPath *path = [UIBezierPath bezierPathWithRect:iconImgV.bounds];
imageViewLayer.shadowPath = path.CGPath;//路徑默認(rèn)為 nil
}
運(yùn)行效果如下
- 如果添加上裁剪圓角的代碼役耕,則陰影不再顯示
imageViewLayer.cornerRadius = 10;
imageViewLayer.masksToBounds = YES;
運(yùn)行效果如下:
在 OffscreenRenderDemo 里采转,僅開啟陰影(沒有指定路徑,同屏數(shù)量10個以上)在滾動時幀率會大幅下降瞬痘,檢測到離屏渲染的黃色特征故慈;指定一個與邊界相同的簡單路徑后離屏渲染特征消失,幀率恢復(fù)正常图云。
下面我們通過代碼驗(yàn)證
- 沒有指定與邊界相同的簡單路徑惯悠,發(fā)生離屏渲染,掉幀較厲害
// 設(shè)置陰影
CALayer *imageViewLayer = imgView.layer;
imageViewLayer.shadowColor = [[UIColor blackColor] CGColor];
imageViewLayer.shadowOpacity = 1.0; //此參數(shù)默認(rèn)為0竣况,即陰影不顯示
imageViewLayer.shadowRadius = 2.0; //給陰影加上圓角克婶,對性能無明顯影響
imageViewLayer.shadowOffset = CGSizeMake(5, 5);
運(yùn)行效果: 開啟離屏渲染監(jiān)視:模擬器 -> Debug -> Color off-screen rendered
FPS 測試結(jié)果
分析:通過運(yùn)行結(jié)果效果圖,我們可以發(fā)現(xiàn)丹泉,在圖片陰影處有黃色情萤,說明該處發(fā)生了離屏渲染。而且 GPU 使用率較高摹恨,會造成卡頓掉幀筋岛。
- 指定一個與邊界相同的簡單路徑后離屏渲染特征消失,幀率恢復(fù)正常晒哄。
//設(shè)定路徑:與視圖的邊界相同
UIBezierPath *path = [UIBezierPath bezierPathWithRect:imgView.bounds];
imageViewLayer.shadowPath = path.CGPath;//路徑默認(rèn)為 nil
運(yùn)行效果: 開啟離屏渲染監(jiān)視:模擬器 -> Debug -> Color off-screen rendered
FPS 測試結(jié)果
分析:通過運(yùn)行結(jié)果效果圖睁宰,我們可以發(fā)現(xiàn),在圖片陰影處沒有黃色寝凌,說明該處沒有發(fā)生離屏渲染柒傻。而且 GPU 使用率不是很高。
除了指定路徑较木,實(shí)現(xiàn)良好性能陰影的方法還有:用圓角優(yōu)化里混合圖層的方法模擬陰影的效果:放一個同樣效果的視圖在要添加陰影程度的視圖的下方红符;使用 Core Graphics 繪制陰影,不過除非萬不得已沒人想碰 Core Graphics API伐债。從實(shí)現(xiàn)成本來講预侯,都不如指定路徑方便。
三 Mask
Mask 效果與混合圖層的效果非常相似峰锁,只是使用同一個遮罩圖像時萎馅,mask 與混合圖層的效果是相反的,在 Demo 里使用反向內(nèi)容的遮罩來實(shí)現(xiàn)圓角虹蒋。實(shí)現(xiàn) mask 效果使用 CALayer 的layer屬性校坑,在 iOS 8 以上可以使用 UIView 的maskView屬性拣技。
- (void)drawUI {
UIImageView *iconImgV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 80, 80)];
iconImgV.image = [UIImage imageNamed:@"icon_girl"];
[self.view addSubview:iconImgV];
[iconImgV mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(iconImgV.size);
make.centerY.equalTo(self.view);
make.centerX.equalTo(self.view);
}];
// 設(shè)置Mask
if (@available(iOS 8.0, *)) {
iconImgV.maskView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"RoundMask"]];
} else {
CALayer *maskLayer = [[CALayer alloc] init];
maskLayer.frame = iconImgV.bounds;
maskLayer.contents = (__bridge id _Nullable)([[UIImage imageNamed:@"RoundMask"] CGImage]);
iconImgV.layer.mask = maskLayer;
}
}
運(yùn)行效果如下:
備注:其中icon_girl.png
和RoundMask
分別為下面兩張圖片
如果所有 maskImage 相同的話,使用一個 maskImage 就夠了耍目,不然每次生成一個新的 UIImage 也會是一個性能隱患點(diǎn)膏斤。注意:可以使用同一個 maskImage,但不能使用同一個 maskView邪驮,不然同時只會有一個 mask 效果莫辨。
在實(shí)戰(zhàn)項(xiàng)目中檢驗(yàn)
- FPS 幀率檢測
通過檢測,F(xiàn)PS 有些掉幀毅访,GPU 使用率較高沮榜。
- 開啟離屏渲染檢測
在圓形圖片區(qū)域我們觀察到有黃色圖形,說明發(fā)生了離屏渲染喻粹。
總結(jié):Mask 效果無法取消離屏渲染蟆融,使用混合圖層的方法來模擬 mask 效果,性能各方面都是和無效果持平守呜。
使用 mask 來實(shí)現(xiàn)圓角時也可以不用圖片型酥,而使用 CAShapeLayer 來指定混合的路徑。
UIBezierPath *roundedRectPath = [UIBezierPath bezierPathWithRoundedRect:iconImgV.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(40, 40)];
CAShapeLayer *shapLayer = [[CAShapeLayer alloc] init];
shapLayer.path = roundedRectPath.CGPath;
iconImgV.layer.mask = shapLayer;
運(yùn)行效果如下:
我們通過設(shè)置
cornerRadii
值來設(shè)置角度值查乒,如果為圓角弥喉,則為圖片尺寸寬高的一半即可。
總結(jié):同樣的 mask 效果使用 CAShapeLayer 時相比直接使用 maskImage 在幀率上稍低玛迄,CPU 利用率無明顯變化由境,但是 GPU 利用率也低一些。
四 GroupOpacity
首先來看看 GroupOpacity 是什么效果:
GroupOpacity 是指 CALayer 的allowsGroupOpacity屬性蓖议,UIView 的alpha屬性等同于 CALayer opacity屬性虏杰。開啟 GroupOpacity 后,子 layer 在視覺上的透明度的上限是其父 layer 的opacity
勒虾。
從 iOS 7 以后默認(rèn)全局開啟了這個功能纺阔,這樣做是為了讓子視圖與其容器視圖保持同樣的透明度。
GroupOpacity 開啟離屏渲染的條件是:layer.opacity != 1.0并且有子 layer 或者背景圖从撼。
- 實(shí)例代碼如下
- (void)drawRedView {
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 50)];
redView.alpha = 0.5;
redView.backgroundColor = [UIColor grayColor];
redView.layer.allowsGroupOpacity = NO;
[self.view addSubview:redView];
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(redView.size);
make.top.equalTo(self.view.mas_top).offset(100);
make.centerX.equalTo(self.view);
}];
// 子視圖
UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 150, 30)];
subView.backgroundColor = [UIColor whiteColor];
[redView addSubview:subView];
[subView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(subView.size);
make.centerX.centerY.equalTo(redView);
}];
}
運(yùn)行效果如下:
圖一是關(guān)閉了
GroupOpacity
屬性州弟,圖二開啟了GroupOpacity
屬性(默認(rèn)開啟的)
這個觸發(fā)條件并不需要subLayer.opacity != 1.0钧栖,非常容易滿足低零。然而在 TableView 這樣的視圖里設(shè)置 cell 或 cell.contentView 的alpha屬性小于1并不能檢測離屏渲染的黃色特征,性能上也沒有明顯差別拯杠。經(jīng)過摸索發(fā)現(xiàn):只有設(shè)置 tableView 的alpha小于1時才會觸發(fā)離屏渲染掏婶,對性能無明顯影響;設(shè)置 cell 的alpha屬性并不會對整體的透明度產(chǎn)生影響潭陪,只有設(shè)置 cell.contentView 才有效雄妥。
五 EdgeAntialiasing
經(jīng)過測試最蕾,開啟 edge antialiasing(旋轉(zhuǎn)視圖并且設(shè)置layer.allowsEdgeAntialiasing = true) 在 iOS 8 和 iOS 9 上并不會觸發(fā)離屏渲染,對性能也沒有什么影響老厌,也許到現(xiàn)在這個功能已經(jīng)被優(yōu)化了瘟则。
// EdgeAntialiasing
redView.layer.allowsEdgeAntialiasing = YES;
六 Rasterization
除了 GroupOpacity
和 EdgeAntialiasing
,其他效果觸發(fā)的離屏渲染都會對性能產(chǎn)生嚴(yán)重影響枝秤,離屏渲染真的是一無是處嗎醋拧?不,離屏渲染本來是個優(yōu)化設(shè)計淀弹。如何物盡其用丹壕?答案是:Rasterization
。
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = cell.layer.contentsScale
shouldRasterize = false時薇溃,離屏渲染的黃色特征僅限于上述自動觸發(fā)離屏渲染的效果的部分菌赖,shouldRasterize = true后該部分和開啟了該屬性的 layer 整體(在這里就是 cell 整體)都有黃色特征,所以開啟 Rasterization 是手動啟動了離屏渲染沐序。
從前面來看琉用,離屏渲染會給 GPU 帶來沉重的負(fù)擔(dān),強(qiáng)制啟動豈不是更糟薄啥?開啟 Rasterization 后辕羽,GPU 只合成一次內(nèi)容,然后復(fù)用合成的結(jié)果垄惧;合成的內(nèi)容超過 100ms 沒有使用會從緩存里移除刁愿,在更新內(nèi)容時還會產(chǎn)生更多的離屏渲染。對于內(nèi)容不發(fā)生變化的視圖到逊,原本拖后腿的離屏渲染就成為了助力铣口;如果視圖內(nèi)容是動態(tài)變化的,使用這個方案有可能讓性能變得更糟觉壶。
Core Animation Instruments 有個
Color Hits Green and Misses Red
的選項(xiàng)脑题,開啟Rasterization
后開啟這個選項(xiàng),屏幕上綠色的部分表示有渲染緩存可用铜靶,紅色的部分表示無渲染緩存可用叔遂。
七 總結(jié)
7.1 造成離屏渲染的原因有
- 設(shè)置
CALayer
的cornerRadius
+masksToBounds
- 設(shè)置
CALayer
的shadowPath
屬性 - 設(shè)置
CALayer
的Mask
屬性 - 設(shè)置
CALayer
的allowsGroupOpacity
屬性設(shè)為YES
并且opacity
小于1 - 設(shè)置
CALayer
的allowsEdgeAntialiasing
屬性
等等.......
7.2 特別注意
- 設(shè)置
shadowPath
陰影的同時,設(shè)置圓角屬性cornerRadius
+masksToBounds
争剿,則陰影不會顯示已艰。 -
RoundedCorner
在僅指定cornerRadius時不會觸發(fā)離屏渲染,僅適用于特殊情況蚕苇;contents為 nil 或者contents不會遮擋背景色圓角哩掺; -
Shawdow
可以通過指定路徑來取消離屏渲染; -
Mask
無法取消離屏渲染涩笤;
以上效果在同等數(shù)量的規(guī)模下嚼吞,對性能的影響等級:Shadow > RoundedCorner > Mask > GroupOpacity(迷之效果)盒件。
任何時候優(yōu)先考慮避免觸發(fā)離屏渲染,無法避免時優(yōu)化方案有兩種:
Rasterization:
適用于靜態(tài)內(nèi)容的視圖舱禽,也就是內(nèi)部結(jié)構(gòu)和內(nèi)容不發(fā)生變化的視圖炒刁,對上面的所有效果而言,在實(shí)現(xiàn)成本以及性能上最均衡的誊稚。即使是動態(tài)變化的視圖切心,開啟 Rasterization 后能夠有效降低 GPU 的負(fù)荷,不過在動態(tài)視圖里是否啟用還是看 Instruments 的數(shù)據(jù)片吊。規(guī)避離屏渲染:
用其他手法來模擬效果绽昏,混合圖層是個性能最好、耗能最少的通用優(yōu)化方案俏脊,尤其對于 rounded corer 和 mask全谤。
本文參考
iOS離屏渲染優(yōu)化
繪制像素到屏幕上