1. 程式人生 > >iOS圖片設定圓角效能優化

iOS圖片設定圓角效能優化

http://www.cnblogs.com/junhuawang/p/5652220.html

問題

圓角雖好,但如果使用不當,它就是你的幀數殺手,特別當它出現在滾動列表的時候。下面來看圓角如何毀掉你的流暢度的。

實測

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計算,但是應用範圍有限。

總結

  1. 在可以使用混合圖層遮擋的場景下,優先使用第四種方法。
  2. 即使是非iOS9以上系統,第一種方法在綜合性能上依然強於後兩者,iOS9以上由於沒有了離屏渲染更是首選。
  3. 方法三由於需要大量計算和增加部分記憶體,需要實際情況各自取捨。