今天來寫一個老生常談的話題衔憨,也是一個面試的高頻問題,我也在面試時不止一次被問到過這個問題——如何高性能的設置圓角袄膏。就用他作為2017年春節(jié)上班之后的第一篇文章践图。
起因
在談及圓角這個話題之前,我們必須先知道系統(tǒng)的API
是怎樣去簡單方便的設置圓角的沉馆。以一個imageView
控件來舉例码党。
imageView.layer.cornerRadius = CGFloat(10);
簡單粗暴,就能設置圓角斥黑。而在這里的一行代碼揖盘,必須為它洗白一件事情,設置圓角的這行代碼锌奴,本身并不會帶來任何的性能損耗兽狭。如果諸位看官看到此處不相信,大可打開Instruments
用Core Animation
來試試看鹿蜀,你就會發(fā)現(xiàn)既沒有Off-Screen Render
,也不會出現(xiàn)掉幀的情況箕慧。至于使用Instruments
來對UIKit
進行分析調試,到時候再寫一篇文章來詳解好了茴恰。
但是颠焦,如果你給一個UILabel
也使用了上面的一行代碼,你會發(fā)現(xiàn)這個UILabel
并不會有任何的變化往枣,可是我們確實實實在在的為它設置了圓角屬性伐庭。也就是說粉渠,很多時候這個屬性對于內部還有子視圖的控件是無能為力的。所以很多時候似忧,我們會這么來設置圓角渣叛。
imageView.layer.cornerRadius = CGFloat(10);
imageView.layer.masksToBounds = YES;
這時候咱們再打開Instruments
去觀察丈秩,惡心的離屏渲染如約而至盯捌。
這里我在稍微贅述一下離屏渲染的概念,什么是離屏渲染呢蘑秽?
討論造成離屏渲染的原因之前饺著,先說明什么是離屏渲染:離屏渲染指的是在圖像在繪制到當前屏幕前,需要先進行一次渲染,之后才繪制到當前屏幕。在第一次渲染時肠牲,GPU(Core Animation)或CPU(Core Graphics)需要額外的一塊內存來進行渲染幼衰,完成后再繪制到屏幕。offscreen到onscreen需要進行上下文切換缀雳,這個切換的性能消耗是昂貴的渡嚣。
因此,我們必須避免不必要的離屏渲染肥印。
造成離屏渲染的原因有:
設置CALayer的cornerRadius识椰,edgeAntialiasingMask,allowsEdgeAntialiasing屬性
把CALayer的maskToBounds設為YES
設置CALayer的shadow屬性
設置CALayer的mask屬性
把CALayer的allowsGroupOpacity屬性設為YES而且opacity小于1
講到這里深碱,大家大可不必對離屏渲染產生巨大的恐慌腹鹉,因為當一個界面的圓角圖片不夠多的時候,對性能的損耗影響基本可以忽略不計敷硅。所以這里的圓角優(yōu)化是針對一屏有很多個圓角的應用來說的功咒。
UIImageView 添加圓角
一般我們最常見的是為UIImageView
添加圓角,首先重要的事情放到前面講绞蹦,千萬避免通過重寫drawRect
方法來設置圓角力奋,不恰當?shù)氖褂眠@個方法,會導致內存的暴增幽七。其次刊侯,這種方法的同樣會導致離屏渲染。
而一個比較理想的實現(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
的黑魔法去設置,在Category 給一個現(xiàn)有的類添加屬性梁厉,但是卻不能添加實例變量辜羊,這似乎成為了 Objective-C的一個明顯短板踏兜。然而值得慶幸的是,我們可以通過 Associated Objects來彌補這一不足八秃。
至于完整的Demo和方法庫碱妆,網上已經有很多了,Github動手搜索吧昔驱。
總結
如果能夠只用 cornerRadius 解決問題疹尾,就不用優(yōu)化。
如果必須設置 masksToBounds骤肛,可以參考圓角視圖的數(shù)量纳本,如果數(shù)量較少(一頁只有幾個)也可以考慮不用優(yōu)化。
UIImageView 的圓角通過直接截取圖片實現(xiàn)腋颠,其它視圖的圓角可以通過 Core Graphics 畫出圓角矩形實現(xiàn)繁成。