高性能設(shè)置圓角,告別離屏渲染

今天來寫一個老生常談的話題,也是一個面試的高頻問題,我也在面試時不止一次被問到過這個問題——如何高性能的設(shè)置圓角。就用他作為2017年春節(jié)上班之后的第一篇文章。

起因

在談及圓角這個話題之前,我們必須先知道系統(tǒng)的API是怎樣去簡單方便的設(shè)置圓角的。以一個imageView控件來舉例。

imageView.layer.cornerRadius = CGFloat(10);

簡單粗暴,就能設(shè)置圓角。而在這里的一行代碼,必須為它洗白一件事情,設(shè)置圓角的這行代碼,本身并不會帶來任何的性能損耗。如果諸位看官看到此處不相信,大可打開InstrumentsCore Animation來試試看,你就會發(fā)現(xiàn)既沒有Off-Screen Render,也不會出現(xiàn)掉幀的情況。至于使用Instruments來對UIKit進行分析調(diào)試,到時候再寫一篇文章來詳解好了。

但是,如果你給一個UILabel也使用了上面的一行代碼,你會發(fā)現(xiàn)這個UILabel并不會有任何的變化,可是我們確實實實在在的為它設(shè)置了圓角屬性。也就是說,很多時候這個屬性對于內(nèi)部還有子視圖的控件是無能為力的。所以很多時候,我們會這么來設(shè)置圓角。

imageView.layer.cornerRadius = CGFloat(10);
imageView.layer.masksToBounds = YES;

這時候咱們再打開Instruments去觀察,惡心的離屏渲染如約而至。

這里我在稍微贅述一下離屏渲染的概念,什么是離屏渲染呢?

討論造成離屏渲染的原因之前,先說明什么是離屏渲染:離屏渲染指的是在圖像在繪制到當(dāng)前屏幕前,需要先進行一次渲染,之后才繪制到當(dāng)前屏幕。在第一次渲染時,GPU(Core Animation)或CPU(Core Graphics)需要額外的一塊內(nèi)存來進行渲染,完成后再繪制到屏幕。offscreen到onscreen需要進行上下文切換,這個切換的性能消耗是昂貴的。

因此,我們必須避免不必要的離屏渲染。

造成離屏渲染的原因有:

  • 設(shè)置CALayer的cornerRadius,edgeAntialiasingMask,allowsEdgeAntialiasing屬性

  • 把CALayer的maskToBounds設(shè)為YES

  • 設(shè)置CALayer的shadow屬性

  • 設(shè)置CALayer的mask屬性

  • 把CALayer的allowsGroupOpacity屬性設(shè)為YES而且opacity小于1

講到這里,大家大可不必對離屏渲染產(chǎn)生巨大的恐慌,因為當(dāng)一個界面的圓角圖片不夠多的時候,對性能的損耗影響基本可以忽略不計。所以這里的圓角優(yōu)化是針對一屏有很多個圓角的應(yīng)用來說的。

UIImageView 添加圓角

一般我們最常見的是為UIImageView添加圓角,首先重要的事情放到前面講,千萬避免通過重寫drawRect方法來設(shè)置圓角,不恰當(dāng)?shù)氖褂眠@個方法,會導(dǎo)致內(nèi)存的暴增。其次,這種方法的同樣會導(dǎo)致離屏渲染。

而一個比較理想的實現(xiàn)思路,是直接截取圖片。

CGSize size = self.bounds.size;
    CGFloat scale = [UIScreen mainScreen].scale;
    CGSize cornerRadii = CGSizeMake(cornerRadius, cornerRadius);
    
    UIGraphicsBeginImageContextWithOptions(size, YES, scale);
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    if (nil == currentContext) {
        return;
    }
    UIBezierPath *cornerPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:rectCornerType cornerRadii:cornerRadii];
    UIBezierPath *backgroundRect = [UIBezierPath bezierPathWithRect:self.bounds];
    [backgroundColor setFill];
    [backgroundRect fill];
    [cornerPath addClip];
    [self.layer renderInContext:currentContext];
    [self drawBorder:cornerPath];
    UIImage *processedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    if (processedImage) {
        objc_setAssociatedObject(processedImage, &kProcessedImage, @(1), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    self.image = processedImage;

上面這段代碼我只是給出了大致的實現(xiàn)思路,圓角路徑直接用貝塞爾曲線繪制,而其中的屬性,使用了runtime的黑魔法去設(shè)置,在Category 給一個現(xiàn)有的類添加屬性,但是卻不能添加實例變量,這似乎成為了 Objective-C的一個明顯短板。然而值得慶幸的是,我們可以通過 Associated Objects來彌補這一不足。

至于完整的Demo和方法庫,網(wǎng)上已經(jīng)有很多了,Github動手搜索吧。

總結(jié)

  1. 如果能夠只用 cornerRadius 解決問題,就不用優(yōu)化。

  2. 如果必須設(shè)置 masksToBounds,可以參考圓角視圖的數(shù)量,如果數(shù)量較少(一頁只有幾個)也可以考慮不用優(yōu)化。

  3. UIImageView 的圓角通過直接截取圖片實現(xiàn),其它視圖的圓角可以通過 Core Graphics 畫出圓角矩形實現(xiàn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 目錄 離屏渲染的本質(zhì)如何設(shè)置圓角(三種方法)Shadow 陰影MaskGroupOpacityEdgeAntial...
    路飛_Luck閱讀 2,107評論 0 9
  • 有很多種framework以及很多種方法的組合可以在屏幕上渲染UI元素,我們在這里討論這個過程中發(fā)生的事情,希望這...
    縱橫而樂閱讀 4,692評論 4 25
  • 繪制像素到屏幕上 answer-huang22 Mar 2014 分享文章 一個像素是如何繪制到屏幕上去的?有很多...
    阿貍旅途T恤閱讀 1,756評論 0 7
  • 今天,黑色現(xiàn)實主義先鋒戲劇《埋葬》確定將作為文學(xué)劇場演出劇目現(xiàn)身第八屆南鑼戲劇節(jié)的唯一指定劇場蓬蒿,一周前就得到消...
    易鹿閱讀 324評論 0 0
  • We’ve now seen the worst crisis and the deepest recession...
    大立柜閱讀 189評論 0 0

友情鏈接更多精彩內(nèi)容