CALayery以及圖像的顯示

  • UIView和CALayer之間的關(guān)系
  • UIView繪制原理
  • 圖像顯示原理
    • UI卡頓病苗、掉幀的原因
    • 解決方案
  • 離屏渲染
    • 何時(shí)觸發(fā)
    • 為什么要避免離屏渲染
    • 具體舉例

UIView和CALayer之間的關(guān)系

首先他們二者之間是一個(gè)什么關(guān)系宾娜?

  • UIView 是對(duì)CALayer的一個(gè)封裝玉组,它為CALayer 提供內(nèi)容以及負(fù)責(zé)處理觸摸等事件蚪黑,參與事件的響應(yīng)鏈
  • CALayer 負(fù)責(zé)顯示內(nèi)容的contents


    出自 WWDC 2012: iOS App Performance: Graphics and Animations

CALayer由背景色backgroundColor帜篇、內(nèi)容contents、邊緣borderWidth&borderColor構(gòu)成伐割,圖片摘自Adjusting a Layer’s Visual Style and Appearance

Adding a border and background to a layer

UIView繪制原理

上面我們說(shuō)過(guò)候味,UIView的內(nèi)容其實(shí)是CALayer顯示的,layer中有一個(gè)屬性contents隔心,而contents的內(nèi)容就是要顯示的具體內(nèi)容白群,通常情況下,contents的值就是一個(gè)位圖硬霍。我們常用的無(wú)論是 UILabel還是UIImageView里面顯示的內(nèi)容帜慢,其實(shí)都是繪制在一張畫(huà)布上,繪制完成從畫(huà)布中導(dǎo)出圖片唯卖,再把圖片賦值給layer.contents就完成了顯示 (推薦看看這篇文章 繪制像素到屏幕上)粱玲,那么UIView內(nèi)部是如何具體繪制的呢?
下面我們通過(guò)一張圖來(lái)詳細(xì)看看:

  • 當(dāng)我們調(diào)用 [UIView setNeedsDisplay]方法時(shí)候拜轨,實(shí)際上系統(tǒng)并沒(méi)有立即觸發(fā)UIView的繪制工作密幔,而是調(diào)用了對(duì)應(yīng)layer的同名方法[view.layer setNeedsDisplay],此處相當(dāng)于在layer上面打了一個(gè)臟標(biāo)記撩轰,然后等到當(dāng)前RunLoop將要結(jié)束的時(shí)候才會(huì)去調(diào)用[CALayer display]方法,此時(shí)才真正的進(jìn)入到了視圖的繪制工作中。
  • [CALayer display]方法內(nèi)部會(huì)首先去判斷layer.delegate是否響應(yīng)displayerLayer:函數(shù)堪嫂,如果響應(yīng)該函數(shù)則進(jìn)入到了異步繪制工作中偎箫;否則進(jìn)入到系統(tǒng)繪制流程中。

上面就是UIView的繪制原理皆串,接下來(lái)我們看一下系統(tǒng)繪制流程是怎樣的

  • 系統(tǒng)繪制流程:

  • CALayer內(nèi)部會(huì)首先創(chuàng)建一個(gè) backing store淹办,也就是我們常說(shuō)的圖形上下文CGContextRef

  • 然后layer會(huì)判斷是否有代理恶复,如果沒(méi)有代理怜森,那么就會(huì)調(diào)用[CALayer drawInCotext:]方法;如果有代理谤牡,會(huì)調(diào)用代理的drawLayer:inContext:方法副硅,然后做當(dāng)前視圖的繪制工作,在一個(gè)合適的時(shí)機(jī)給與我們這個(gè)十分熟悉的 [UIView drawRect:]方法的回調(diào)翅萤,該方法內(nèi)部默認(rèn)是什么都不做的恐疲,系統(tǒng)給我們開(kāi)這個(gè)口子是為了讓我們可以再做一些其他的繪制工作

  • 無(wú)論走哪個(gè)分支,最終都會(huì)由 CALayer上傳對(duì)應(yīng)的backing store給GPU套么,此時(shí)結(jié)束系統(tǒng)默認(rèn)的繪制流程

  • 異步繪制流程:
    實(shí)際上我們只需要調(diào)用系統(tǒng)給我們提供的代理方法 [layer.delegate displayLayer:]即可培己,在該方法中生成對(duì)應(yīng)的 bitmap(位圖),同時(shí)設(shè)置bitmap作為layer.contents屬性的值胚泌。

具體流程如下:


  • 假如此時(shí)調(diào)用[view setNeedsDisplay]方法省咨,系統(tǒng)會(huì)在當(dāng)前RunLoop將要結(jié)束的時(shí)候調(diào)用[CALyer display]方法,如果此時(shí)我們實(shí)現(xiàn)了displayLayer :這個(gè)方法玷室,我們可以在該方法內(nèi)部創(chuàng)建一個(gè)子線(xiàn)程去做一些位圖的繪制工作零蓉,主線(xiàn)程可以去做一些其他的操作

  • 當(dāng)位圖繪制完成后在回到主線(xiàn)程,設(shè)置layer的contents屬性阵苇,這樣就完成了一個(gè)UI控件的異步繪制過(guò)程

圖像顯示原理


計(jì)算機(jī)系統(tǒng)中的CPU和GPU這兩個(gè)硬件是通過(guò)總線(xiàn)連接起來(lái)的壁公,在CPU中輸出的結(jié)果一般都是一個(gè)位圖,在經(jīng)由總線(xiàn)在一個(gè)合適的時(shí)機(jī)上傳給GPU绅项,GPU拿到這個(gè)位圖之后會(huì)做一些圖層的渲染紊册,包括一些紋理的合成等,之后將這個(gè)結(jié)果放入到FrameBuffer即幀緩沖區(qū)中快耿,隨后視頻控制器會(huì)按照 VSync 信號(hào)在指定時(shí)間之前提取對(duì)應(yīng)幀緩沖區(qū)的數(shù)據(jù)顯示內(nèi)容囊陡,最后顯示到我們的手機(jī)屏幕上面。

下面我們來(lái)看看在這個(gè)過(guò)程中CPU和GPU分別都做了哪些工作


首先當(dāng)我們創(chuàng)建一個(gè)UIView控件的時(shí)候掀亥,其中負(fù)責(zé)顯示內(nèi)容的是 CALayer
CALayer中有一個(gè)contents屬性撞反,就是我們最終要繪制到屏幕上的一個(gè)位圖,比如說(shuō)我們創(chuàng)建了一個(gè)UILabel搪花,那么在contents里面就放了一個(gè)關(guān)于Hello world的文字位圖
然后系統(tǒng)會(huì)在一個(gè)合適的時(shí)機(jī)回調(diào)給我們一個(gè)drawRect:的方法遏片,這個(gè)方法中我們可以去繪制一些自定義的內(nèi)容
繪制好了之后嘹害,最終會(huì)由Core Animation這個(gè)框架提交給GPU部分的OpenGL渲染管線(xiàn),進(jìn)行最終的位圖的渲染吮便,包括紋理合成等笔呀,然后顯示在屏幕上

CPU的工作

  • Layout階段: UI布局、文本的計(jì)算等
  • Display階段: 繪制髓需,比如 drawRect:方法就是發(fā)生在這一過(guò)程中
  • Prepare階段 圖片的編解碼等许师。比如我們使用到了UIImageView對(duì)象,對(duì)它設(shè)置Image對(duì)象的時(shí)候僚匆,默認(rèn)情況下它是不能直接顯示到我們屏幕上面的微渠,需要對(duì)圖片進(jìn)行一個(gè)解碼操作。
  • Commit: 最后這一步經(jīng)由Core Animation框架將最終生成的位圖提交給GPU咧擂。

GPU渲染管線(xiàn)的工作

UI卡頓逞盆、掉幀的原因

我們平時(shí)在做性能優(yōu)化的時(shí)候,經(jīng)常會(huì)提到一個(gè)指標(biāo)就是頁(yè)面幀率達(dá)到60fps屋确,至于為什么是60fps纳击,我還真去網(wǎng)上特意查了一下,放到了下面

生成圖像的設(shè)備(如顯卡)與顯示圖像的設(shè)備(如顯示器)是分離的攻臀。
目前大多數(shù)顯示器根據(jù)其設(shè)定按 30Hz焕数、 60Hz、 120Hz 或者 144Hz 的頻率進(jìn)行刷新刨啸。 而其中最常見(jiàn)的刷新頻率是 60 Hz堡赔。 這樣做是為了繼承以前電視機(jī)刷新頻率為 60Hz 的設(shè)定旷痕。
顯卡內(nèi)圖片的真正提供者是GPU乾胶,由于 GPU 生成圖像的頻率與顯示器刷新的頻率是不相關(guān)的,那么在顯示器刷新時(shí)池户,GPU 沒(méi)有準(zhǔn)備好需要顯示的圖像怎么辦离例;或者 GPU 的渲染速度過(guò)快换团,顯示器來(lái)不及刷新,GPU 就已經(jīng)開(kāi)始渲染下一幀圖像又該如何處理宫蛆?
如果解決不了這兩個(gè)問(wèn)題艘包,就會(huì)出現(xiàn)屏幕撕裂(Screen Tearing)現(xiàn)象,即屏幕中一部分顯示的是上一幀的內(nèi)容耀盗,另一部分顯示的是下一幀的內(nèi)容想虎。

如何解決屏幕撕裂問(wèn)題?其中最知名可能也是最古老的解決方案就是 V-Sync 技術(shù)
V-Sync 的原理簡(jiǎn)單而直觀:產(chǎn)生屏幕撕裂的原因是顯卡在屏幕刷新時(shí)進(jìn)行了渲染叛拷,而 V-Sync 通過(guò)同步渲染/刷新時(shí)間的方式來(lái)解決這個(gè)問(wèn)題舌厨。顯示器的刷新頻率為 60 Hz,若此時(shí)開(kāi)啟 V-Sync忿薇,將控制顯卡渲染速度在 60 Hz 以?xún)?nèi)以匹配顯示器刷新頻率裙椭。這也意味著躏哩,在 V-Sync 的限制下,顯卡顯示性能的極限就限制為 60 Hz 以?xún)?nèi)

參考:腦洞大開(kāi):為啥幀率達(dá)到 60 fps 就流暢揉燃?

60fps指的是每一秒鐘有60幀的畫(huà)面更新震庭,我們?nèi)搜壑锌吹降木褪橇鲿车男Ч诖艘簿褪敲块g隔1/60也就是16.7ms就會(huì)產(chǎn)生一幀畫(huà)面你雌,在這16.7ms內(nèi)需要由CPU和GPU協(xié)同工作產(chǎn)生這一幀的數(shù)據(jù),

卡頓掉幀原因:

如果在這16.7ms內(nèi)二汛,也就是在下一幀VSync信號(hào)到來(lái)之前婿崭,CPU和GPU并沒(méi)有共同完成下一幀畫(huà)面的合成,于是那一幀就會(huì)被丟棄肴颊,也就是掉幀現(xiàn)象氓栈,而這時(shí)顯示屏?xí)A糁暗膬?nèi)容不變,從用戶(hù)的角度來(lái)看就是造成了屏幕卡頓婿着。

基于以上的分析我們可以從CPU和GPU兩部分來(lái)處理

CPU 資源消耗原因和解決方案

對(duì)象創(chuàng)建
對(duì)象的創(chuàng)建授瘦、設(shè)置屬性、甚至還有讀取文件等操作竟宋,比較消耗 CPU 資源提完。盡量用輕量的對(duì)象代替重量的對(duì)象,比如 CALayer 比 UIView 要輕量許多丘侠,那么不需要響應(yīng)觸摸事件的控件徒欣,用 CALayer 顯示會(huì)更加合適。如果對(duì)象不涉及 UI 操作蜗字,則盡量放到后臺(tái)線(xiàn)程去創(chuàng)建打肝,但可惜的是包含有 CALayer 的控件,都只能在主線(xiàn)程創(chuàng)建和操作挪捕。通過(guò) Storyboard 創(chuàng)建視圖對(duì)象時(shí)粗梭,其資源消耗會(huì)比直接通過(guò)代碼創(chuàng)建對(duì)象要大非常多,在性能敏感的界面里级零,Storyboard 并不是一個(gè)好的技術(shù)選擇断医。

如果對(duì)象可以復(fù)用,并且復(fù)用的代價(jià)比釋放妄讯、創(chuàng)建新對(duì)象還要小孩锡,那么這類(lèi)對(duì)象應(yīng)當(dāng)盡量放到一個(gè)緩存池里復(fù)用。

對(duì)象調(diào)整
對(duì)象的調(diào)整也經(jīng)常是消耗 CPU 資源的地方亥贸。這里特別說(shuō)一下 CALayer躬窜,CALayer 內(nèi)部并沒(méi)有屬性,當(dāng)調(diào)用屬性方法時(shí)炕置,它內(nèi)部是通過(guò)運(yùn)行時(shí) resolveInstanceMethod為對(duì)象臨時(shí)添加一個(gè)方法荣挨,并把對(duì)應(yīng)屬性值保存到內(nèi)部的一個(gè) Dictionary 里男韧,同時(shí)還會(huì)通知 delegate、創(chuàng)建動(dòng)畫(huà)等等默垄,非常消耗資源此虑。UIView 的關(guān)于顯示相關(guān)的屬性(比如frame/bounds/transform)等實(shí)際上都是 CALayer 屬性映射來(lái)的,所以對(duì) UIView 的這些屬性進(jìn)行調(diào)整時(shí)口锭,消耗的資源要遠(yuǎn)大于一般的屬性朦前。對(duì)此你在應(yīng)用中,應(yīng)該盡量減少不必要的屬性修改鹃操。

布局計(jì)算
視圖布局的計(jì)算是 App 中最為常見(jiàn)的消耗 CPU 資源的地方韭寸。如果能在后臺(tái)線(xiàn)程提前計(jì)算好視圖布局、并且對(duì)視圖布局進(jìn)行緩存荆隘,那么這個(gè)地方基本就不會(huì)產(chǎn)生性能問(wèn)題了恩伺。

不論通過(guò)何種技術(shù)對(duì)視圖進(jìn)行布局,其最終都會(huì)落到對(duì) UIView.frame/bounds/center等屬性的調(diào)整上椰拒。上面也說(shuō)過(guò)晶渠,對(duì)這些屬性的調(diào)整非常消耗資源,所以盡量提前計(jì)算好布局燃观,在需要時(shí)一次性調(diào)整好對(duì)應(yīng)屬性褒脯,而不要多次、頻繁的計(jì)算和調(diào)整這些屬性仪壮。
當(dāng)獲取到 API JSON 數(shù)據(jù)后憨颠,我會(huì)把每條 Cell 需要的數(shù)據(jù)都在后臺(tái)線(xiàn)程計(jì)算并封裝為一個(gè)布局對(duì)象 CellLayout。CellLayout 包含所有文本的 CoreText 排版結(jié)果积锅、Cell 內(nèi)部每個(gè)控件的高度爽彤、Cell 的整體高度。每個(gè) CellLayout 的內(nèi)存占用并不多缚陷,所以當(dāng)生成后适篙,可以全部緩存到內(nèi)存,以供稍后使用箫爷。這樣嚷节,TableView 在請(qǐng)求各個(gè)高度函數(shù)時(shí),不會(huì)消耗任何多余計(jì)算量虎锚;當(dāng)把 CellLayout 設(shè)置到 Cell 內(nèi)部時(shí)硫痰,Cell 內(nèi)部也不用再計(jì)算布局了。

對(duì)于通常的 TableView 來(lái)說(shuō)窜护,提前在后臺(tái)計(jì)算好布局結(jié)果是非常重要的一個(gè)性能優(yōu)化點(diǎn)效斑。為了達(dá)到最高性能,你可能需要犧牲一些開(kāi)發(fā)速度柱徙,不要用 Autolayout 等技術(shù)缓屠,少用 UILabel 等文本控件奇昙。但如果你對(duì)性能的要求并不那么高,可以嘗試用 TableView 的預(yù)估高度的功能敌完,并把每個(gè) Cell 高度緩存下來(lái)储耐。這里有個(gè)來(lái)自百度知道團(tuán)隊(duì)的開(kāi)源項(xiàng)目可以很方便的幫你實(shí)現(xiàn)這一點(diǎn):FDTemplateLayoutCell

文本渲染
屏幕上能看到的所有文本內(nèi)容控件滨溉,包括 UIWebView什湘,在底層都是通過(guò) CoreText 排版、繪制為 Bitmap 顯示的晦攒。常見(jiàn)的文本控件 (UILabel禽炬、UITextView 等),其排版和繪制都是在主線(xiàn)程進(jìn)行的勤家,當(dāng)顯示大量文本時(shí),CPU 的壓力會(huì)非常大柳恐。對(duì)此解決方案只有一個(gè)伐脖,那就是自定義文本控件,用 TextKit 或最底層的 CoreText 對(duì)文本異步繪制乐设。盡管這實(shí)現(xiàn)起來(lái)非常麻煩讼庇,但其帶來(lái)的優(yōu)勢(shì)也非常大,CoreText 對(duì)象創(chuàng)建好后近尚,能直接獲取文本的寬高等信息蠕啄,避免了多次計(jì)算(調(diào)整 UILabel 大小時(shí)算一遍、UILabel 繪制時(shí)內(nèi)部再算一遍)戈锻;CoreText 對(duì)象占用內(nèi)存較少歼跟,可以緩存下來(lái)以備稍后多次渲染。

圖像解碼
當(dāng)你用UIImageCGImageSource 的那幾個(gè)方法創(chuàng)建圖片時(shí)格遭,圖片數(shù)據(jù)并不會(huì)立刻解碼哈街。圖片設(shè)置到UIImageView或者 CALayer.contents中去,并且 CALayer 被提交到 GPU 前拒迅,CGImage 中的數(shù)據(jù)才會(huì)得到解碼骚秦。這一步是發(fā)生在主線(xiàn)程的,并且不可避免璧微。如果想要繞開(kāi)這個(gè)機(jī)制作箍,常見(jiàn)的做法是在后臺(tái)線(xiàn)程先把圖片繪制到 CGBitmapContext 中,然后從 Bitmap 直接創(chuàng)建圖片前硫。目前常見(jiàn)的網(wǎng)絡(luò)圖片庫(kù)都自帶這個(gè)功能胞得。

圖像的繪制
圖像的繪制通常是指用那些以 CG 開(kāi)頭的方法把圖像繪制到畫(huà)布中,然后從畫(huà)布創(chuàng)建圖片并顯示這樣一個(gè)過(guò)程开瞭。這個(gè)最常見(jiàn)的地方就是[UIView drawRect:]里面了懒震。由于CoreGraphic方法通常都是線(xiàn)程安全的罩息,所以圖像的繪制可以很容易的放到后臺(tái)線(xiàn)程進(jìn)行。一個(gè)簡(jiǎn)單異步繪制的過(guò)程大致如下(實(shí)際情況會(huì)比這個(gè)復(fù)雜得多个扰,但原理基本一致):

- (void)display {
    dispatch_async(backgroundQueue, ^{
        CGContextRef ctx = CGBitmapContextCreate(...);
        // draw in context...
        CGImageRef img = CGBitmapContextCreateImage(ctx);
        CFRelease(ctx);
        dispatch_async(mainQueue, ^{
            layer.contents = img;
        });
    });
}

GPU 資源消耗原因和解決方案

相對(duì)于 CPU 來(lái)說(shuō)瓷炮,GPU 能干的事情比較單一:接收提交的紋理(Texture)和頂點(diǎn)描述(三角形),應(yīng)用變換(transform)递宅、混合并渲染娘香,然后輸出到屏幕上。通常你所能看到的內(nèi)容办龄,主要也就是紋理(圖片)和形狀(三角模擬的矢量圖形)兩類(lèi)

紋理的渲染
所有的 Bitmap烘绽,包括圖片、文本俐填、柵格化的內(nèi)容安接,最終都要由內(nèi)存提交到顯存,綁定為 GPU Texture英融。不論是提交到顯存的過(guò)程盏檐,還是 GPU 調(diào)整和渲染 Texture 的過(guò)程,都要消耗不少 GPU 資源驶悟。當(dāng)在較短時(shí)間顯示大量圖片時(shí)(比如 TableView 存在非常多的圖片并且快速滑動(dòng)時(shí))胡野,CPU 占用率很低,GPU 占用非常高痕鳍,界面仍然會(huì)掉幀硫豆。避免這種情況的方法只能是盡量減少在短時(shí)間內(nèi)大量圖片的顯示,盡可能將多張圖片合成為一張進(jìn)行顯示笼呆。

當(dāng)圖片過(guò)大熊响,超過(guò) GPU 的最大紋理尺寸時(shí),圖片需要先由 CPU 進(jìn)行預(yù)處理诗赌,這對(duì) CPU 和 GPU 都會(huì)帶來(lái)額外的資源消耗耘眨。目前來(lái)說(shuō),iPhone 4S 以上機(jī)型境肾,紋理尺寸上限都是 4096×4096剔难,更詳細(xì)的資料可以看這里:iosres.com。所以奥喻,盡量不要讓圖片和視圖的大小超過(guò)這個(gè)值偶宫。

視圖的混合 (Composing)
當(dāng)多個(gè)視圖(或者說(shuō) CALayer)重疊在一起顯示時(shí),GPU 會(huì)首先把他們混合到一起环鲤。如果視圖結(jié)構(gòu)過(guò)于復(fù)雜纯趋,混合的過(guò)程也會(huì)消耗很多 GPU 資源。為了減輕這種情況的 GPU 消耗,應(yīng)用應(yīng)當(dāng)盡量減少視圖數(shù)量和層次吵冒,并在不透明的視圖里標(biāo)明 opaque 屬性以避免無(wú)用的 Alpha 通道合成纯命。當(dāng)然,這也可以用上面的方法痹栖,把多個(gè)視圖預(yù)先渲染為一張圖片來(lái)顯示

圖形的生成
CALayer 的 border亿汞、圓角、陰影揪阿、遮罩(mask)疗我,CASharpLayer 的矢量圖形顯示,通常會(huì)觸發(fā)離屏渲染offscreen rendering)南捂,而離屏渲染通常發(fā)生在 GPU 中吴裤。當(dāng)一個(gè)列表視圖中出現(xiàn)大量圓角的 CALayer,并且快速滑動(dòng)時(shí)溺健,可以觀察到 GPU 資源已經(jīng)占滿(mǎn)麦牺,而 CPU 資源消耗很少。這時(shí)界面仍然能正潮掮裕滑動(dòng)枕面,但平均幀數(shù)會(huì)降到很低。為了避免這種情況缚去,可以嘗試開(kāi)啟 CALayer.shouldRasterize屬性,但這會(huì)把原本離屏渲染的操作轉(zhuǎn)嫁到 CPU 上去琼开。對(duì)于只需要圓角的某些場(chǎng)合易结,也可以用一張已經(jīng)繪制好的圓角圖片覆蓋到原本視圖上面來(lái)模擬相同的視覺(jué)效果。最徹底的解決辦法柜候,就是把需要顯示的圖形在后臺(tái)線(xiàn)程繪制為圖片搞动,避免使用圓角、陰影渣刷、遮罩等屬性鹦肿。

推薦大家看下面這個(gè)博客,寫(xiě)的真是超詳細(xì)8ú瘛B崂!!
iOS 保持界面流暢的技巧

離屏渲染

GPU進(jìn)行屏幕渲染有兩種方式

  • On-Screen Rendering(在屏渲染):指的是GPU的渲染操作是在當(dāng)前用于顯示的屏幕緩沖區(qū)進(jìn)行
  • Off-Screen Rendering (離屏渲染):指的是GPU在當(dāng)前屏幕緩沖區(qū)以外新開(kāi)辟一個(gè)緩沖區(qū)進(jìn)行渲染操作

何時(shí)觸發(fā)碌嘀?
當(dāng)我們指定了UI視圖的某些屬性涣旨,比如圓角、圖層蒙版股冗、陰影霹陡,光柵化的時(shí)候,圖層屬性的混合體也就是紋理的合成在被指定為在未預(yù)合成之前(下一個(gè)VSync信號(hào)到來(lái)之前)不能直接在屏幕中繪制,所以就需要屏幕外渲染烹棉。

為什么要避免離屏渲染攒霹?

一般情況下,你需要避免離屏渲染浆洗,因?yàn)檫@是很大的消耗催束。直接將圖層合成到幀的緩沖區(qū)中(在屏幕上)比先創(chuàng)建屏幕外緩沖區(qū),然后渲染到紋理中辅髓,最后將結(jié)果渲染到幀的緩沖區(qū)中要廉價(jià)很多泣崩。因?yàn)檫@其中涉及兩次昂貴的環(huán)境轉(zhuǎn)換(轉(zhuǎn)換環(huán)境到屏幕外緩沖區(qū),然后轉(zhuǎn)換環(huán)境到幀緩沖區(qū))洛口。

上文摘自 繪制像素到屏幕上

總的來(lái)說(shuō)觸發(fā)離屏渲染會(huì)增加GPU的工作量矫付,從而可能導(dǎo)致CPU和GPU在繪制一幀畫(huà)面的工作時(shí)長(zhǎng)總超過(guò)了16.7ms,從而導(dǎo)致UI的卡頓和掉幀第焰。

觸發(fā)場(chǎng)景介紹以及優(yōu)化
我們可以在真機(jī)情況下使用XCode下的 Debug -> View Debugging ->Rendering ->Color Offscreen-Rendered Yellow的選項(xiàng)买优,它會(huì)將已經(jīng)被渲染到屏幕外緩沖區(qū)的區(qū)域標(biāo)注為黃色(這個(gè)選項(xiàng)在模擬器中也可以用)。

設(shè)置圓角

我們看看官方對(duì)cornerRadius的解釋?zhuān)?/p>

When positive, the background of the layer will be drawn with rounded corners. Also effects the mask generated by the `masksToBounds' property. Defaults to zero. Animatable.

很明顯cornerRadius只對(duì)前景框和背景色起作用挺举,如果Contents 有內(nèi)容或者內(nèi)容的背景不是透明的話(huà)杀赢,只有設(shè)置masksToBounds = Yes;才能起作用,此時(shí)兩個(gè)屬性相結(jié)合湘纵,產(chǎn)生離屏渲染脂崔。

  • UIView
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(150, 30, 100, 100)];
    view.backgroundColor = [UIColor lightGrayColor];
    view.layer.cornerRadius = 25.f;
    view.layer.borderColor = [UIColor redColor].CGColor;
    view.layer.borderWidth = 1;
    [self.view addSubview:view];

如上所示,為UIView 單獨(dú)設(shè)置cornerRadius并沒(méi)有觸發(fā)離屏渲染梧喷,同時(shí)也可以達(dá)到圓角的效果砌左。

  • UILabel
    UIColor *color = [UIColor blueColor];
    UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(25, 30, 100, 60)];
    lbl.text = @"Developer";
    lbl.textColor = color;
    lbl.layer.borderColor = color.CGColor;
    lbl.layer.borderWidth = 1;
    lbl.textAlignment = NSTextAlignmentCenter;
    lbl.layer.cornerRadius = 30;
    lbl.layer.backgroundColor = [UIColor redColor].CGColor;
    [self.view addSubview:lbl];


我們?yōu)?code>UILabel 單獨(dú)設(shè)置cornerRadius也沒(méi)有觸發(fā)離屏渲染,如果我們添加上 lbl.layer.masksToBounds = YES;之后:

很明顯此時(shí)觸發(fā)了離屏渲染

  • UIImageView
    UIImageView *img = [[UIImageView alloc] initWithFrame:CGRectMake(25, 30, 100, 100)];
    img.image = [UIImage imageNamed:@"dog"];
    img.backgroundColor = [UIColor redColor];//加上背景色會(huì)觸發(fā)離屏渲染
    img.layer.cornerRadius = 20;
    img.layer.masksToBounds = YES;//必須加上這一句
    [self.view addSubview:img];

UIViewUILabel不同铺敌,單獨(dú)設(shè)置img.layer.cornerRadius = 20是看不出顯示圓角效果的汇歹,因?yàn)閳D片的image屬性是img.layerContents部分;只有將layer的masksToBounds屬性也設(shè)置為YES偿凭,才能繪制出圓角效果产弹。
但是從iOS9以后蘋(píng)果對(duì)UIImageView做了一些優(yōu)化,為圖片設(shè)置圓角不會(huì)在觸發(fā)離屏渲染了弯囊,前提是不要為圖片設(shè)置背景顏色痰哨,相信一般也沒(méi)有人這么操作。

沒(méi)有添加背景色

添加了背景色

  • UIButton
    UIViewUILabel一樣匾嘱,我們可以單獨(dú)使用layer.cornerRadius為按鈕設(shè)置圓角作谭,而不需要設(shè)置layer.maskstoBounds
    但是如果我們?yōu)榘粹o設(shè)置了image或者BackgroundImage屬性的時(shí)候就必須結(jié)合layer.masksToBounds = YES圓角才能生效奄毡,道理同上折欠,因?yàn)?code>image屬性是img.layerContents部分
    如下代碼:
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    [btn setTitle:@"確定" forState:UIControlStateNormal];
    btn.frame = CGRectMake(25, 30, 100, 40);
    btn.layer.cornerRadius = 15;
    btn.layer.masksToBounds = YES;
    UIImage *img = [self imageWithColor:[UIColor orangeColor] rect:btn.bounds];
    [btn setBackgroundImage:img forState:UIControlStateNormal];//此時(shí)會(huì)觸發(fā)離屏渲染
    [self.view addSubview:btn];

為按鈕設(shè)置了圖片或者是背景圖片之后 在進(jìn)行 layer.masksToBounds = YES操作就會(huì)觸發(fā)離屏渲染,目前我還沒(méi)有找到好的解決方法,如果有朋友知道可以告訴我哈~

設(shè)置陰影
   UIView *view = [[UIView alloc] initWithFrame:CGRectMake(25, 30, 100, 100)];
    view.backgroundColor = [UIColor lightGrayColor];
    view.layer.shadowColor = [UIColor redColor].CGColor;
    view.layer.shadowOffset = CGSizeMake(10, 10);
    view.layer.shadowOpacity = 0.3;
    [self.view addSubview:view];

但是我們可以為CALayer的 設(shè)置 shadowPath,添加上下面這句代碼后

view.layer.shadowPath = [UIBezierPath bezierPathWithRect:view.bounds].CGPath;

此時(shí)就不會(huì)產(chǎn)生離屏渲染了


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锐秦,一起剝皮案震驚了整個(gè)濱河市咪奖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酱床,老刑警劉巖羊赵,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異扇谣,居然都是意外死亡昧捷,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)罐寨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)靡挥,“玉大人,你說(shuō)我怎么就攤上這事鸯绿“掀疲” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵瓶蝴,是天一觀的道長(zhǎng)毒返。 經(jīng)常有香客問(wèn)我,道長(zhǎng)舷手,這世上最難降的妖魔是什么拧簸? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮男窟,結(jié)果婚禮上盆赤,老公的妹妹穿的比我還像新娘。我一直安慰自己蝎宇,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布祷安。 她就那樣靜靜地躺著姥芥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汇鞭。 梳的紋絲不亂的頭發(fā)上凉唐,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音霍骄,去河邊找鬼台囱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛读整,可吹牛的內(nèi)容都是我干的簿训。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼强品!你這毒婦竟也來(lái)了膘侮?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤的榛,失蹤者是張志新(化名)和其女友劉穎琼了,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體夫晌,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雕薪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晓淀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片所袁。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖要糊,靈堂內(nèi)的尸體忽然破棺而出纲熏,到底是詐尸還是另有隱情,我是刑警寧澤锄俄,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布局劲,位于F島的核電站,受9級(jí)特大地震影響奶赠,放射性物質(zhì)發(fā)生泄漏鱼填。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一毅戈、第九天 我趴在偏房一處隱蔽的房頂上張望苹丸。 院中可真熱鬧,春花似錦苇经、人聲如沸赘理。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)商模。三九已至,卻和暖如春蜘澜,著一層夾襖步出監(jiān)牢的瞬間施流,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工鄙信, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞪醋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓装诡,卻偏偏與公主長(zhǎng)得像银受,于是被迫代替她去往敵國(guó)和親践盼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容