- 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
CALayer由背景色backgroundColor帜篇、內(nèi)容contents、邊緣borderWidth&borderColor
構(gòu)成伐割,圖片摘自Adjusting a Layer’s Visual Style and Appearance
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)你用UIImage
或CGImageSource
的那幾個(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];
與UIView
和 UILabel
不同铺敌,單獨(dú)設(shè)置img.layer.cornerRadius = 20
是看不出顯示圓角效果的汇歹,因?yàn)閳D片的image
屬性是img.layer
的Contents
部分;只有將layer的masksToBounds
屬性也設(shè)置為YES偿凭,才能繪制出圓角效果产弹。
但是從iOS9
以后蘋(píng)果對(duì)UIImageView
做了一些優(yōu)化,為圖片設(shè)置圓角不會(huì)在觸發(fā)離屏渲染了弯囊,前提是不要為圖片設(shè)置背景顏色痰哨,相信一般也沒(méi)有人這么操作。
- UIButton
與UIView
和UILabel
一樣匾嘱,我們可以單獨(dú)使用layer.cornerRadius
為按鈕設(shè)置圓角作谭,而不需要設(shè)置layer.maskstoBounds
。
但是如果我們?yōu)榘粹o設(shè)置了image
或者BackgroundImage
屬性的時(shí)候就必須結(jié)合layer.masksToBounds = YES
圓角才能生效奄毡,道理同上折欠,因?yàn)?code>image屬性是img.layer
的Contents
部分
如下代碼:
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)生離屏渲染了