問題
圓角雖好,但如果使用不當,它就是你的幀數殺手,特別當它出現在滾動列表的時候。下面來看圓角如何毀掉你的流暢度的。
實測
layer.cornerRadius
我創建了一個簡單地UITableView視圖,為每個cell添加了2個UIImageView實例,且為UIImageView實例進行如下設置
aImageView.layer.cornerRadius = aImageView.frame.size.width/2.0;
aImageView.layer.masksToBounds = YES;
運行截圖如下:

你們猜,現在滾動的幀率是多少。

已經跌至45幀每秒,這個幀率已經讓人感覺到不那么順滑了,如果低於40幀每秒,普通用戶就會察覺明顯的不流暢了。當我把cell的UIImageView實例增加至四個

現在幀率已經低於30幀每秒了

這個幀率如果出現在首屏,足以引領你的app進入垃圾級別的體驗了。 現在我把UIImageView實例的size調的小一些。

平均幀率提高了大概3幀每秒。

在這里視圖和圓角的大小對幀率並沒有什么卵影響,數量才是傷害的核心輸出啊。
原理
上面拖慢幀率的原因其實都是Off-Screen Rendering(離屏渲染)的原因。離屏渲染是個好東西,但是頻繁發生離屏渲染是非常耗時的。
Off-Screen Rendering
離屏渲染,指的是GPU在當前屏幕緩沖區以外新開辟一個緩沖區進行渲染操作。由上面的一個結論視圖和圓角的大小對幀率並沒有什么卵影響,數量才是傷害的核心輸出啊。可以知道離屏渲染耗時是發生在離屏這個動作上面,而不是渲染。為什么離屏這么耗時?原因主要有創建緩沖區和上下文切換。創建新的緩沖區代價都不算大,付出最大代價的是上下文切換。
上下文切換
上下文切換,不管是在GPU渲染過程中,還是一直所熟悉的進程切換,上下文切換在哪里都是一個相當耗時的操作。首先我要保存當前屏幕渲染環境,然后切換到一
個新的繪制環境,申請繪制資源,初始化環境,然后開始一個繪制,繪制完畢后銷毀這個繪制環境,如需要切換到On-Screen
Rendering或者再開始一個新的離屏渲染重復之前的操作。 下圖描述了一次mask的渲染操作。

一次mask發生了兩次離屏渲染和一次主屏渲染。即使忽略昂貴的上下文切換,一次mask需要渲染三次才能在屏幕上顯示,這已經是普通視圖顯示3陪耗時,若
再加上下文環境切換,一次mask就是普通渲染的30倍以上耗時操作。問我這個30倍以上這個數據怎么的出來的?當我在cell的UIImageView
的實例增加到150個,並去掉圓角的時候,幀數才跌至28幀每秒。雖然不是甚准確,但至少反映mask這個耗時是無mask操作的耗時的數十倍的。
第一種:設置CALayer的cornerRadius
imageView.image = [UIImage imageNamed:@"img"]; imageView.image.layer.cornerRadius = 5; imageView.image.layer.masksToBounds = YES;
這樣設置會觸發離屏渲染,比較消耗性能。比如當一個頁面上有十幾頭像這樣設置了圓角
會明顯感覺到卡頓。
這種就是最常用的,也是最耗性能的。
注意:ios9.0之后對UIImageView的圓角設置做了優化,UIImageView這樣設置圓角
不會觸發離屏渲染,ios9.0之前還是會觸發離屏渲染。而UIButton還是都會觸發離屏渲染。
第二種
imageView.clipsToBounds = YES; imageView.layer setCornerRadius:50]; imageView.layer.shouldRasterize = YES;
shouldRasterize=YES設置光柵化,可以使離屏渲染的結果緩存到內存中存為位圖, 使用的時候直接使用緩存,節省了一直離屏渲染損耗的性能。
但是如果layer及sublayers常常改變的話,它就會一直不停的渲染及刪除緩存重新 創建緩存,所以這種情況下建議不要使用光柵化,這樣也是比較損耗性能的。
第三種 :通過Core Graphics重新繪制帶圓角的視圖
這種方式性能最好,但是UIButton上不知道怎么繪制,可以用UIimageView添加個 點擊手勢當做UIButton使用
@implementation UIImage (CircleImage) - (UIImage *)drawCircleImage { UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
[[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:50] addClip]; [self drawInRect:self.bounds];
UIImage *output = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return output;
}
@end
//在需要圓角時調用如下 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ UIImage *img = [[UIImage imageNamed:@"image.png"] drawCircleImage]; dispatch_async(dispatch_get_main_queue(), ^{ imageView.image = img; }); });
四、通過混合圖層
此方法就是在要添加圓角的視圖上再疊加一個部分透明的視圖,只對圓角部分進行遮擋。圖層混合的透明度處理方式與mask正好相反。此方法雖然是最優解,沒有離屏渲染,沒有額外的CPU計算,但是應用范圍有限。
總結
- 在可以使用混合圖層遮擋的場景下,優先使用第四種方法。
- 即使是非iOS9以上系統,第一種方法在綜合性能上依然強於后兩者,iOS9以上由於沒有了離屏渲染更是首選。
- 方法三由於需要大量計算和增加部分內存,需要實際情況各自取舍。