在第14章『圖像IO』討論如何高效地載入和顯示圖像谴供,通過(guò)視圖來(lái)避免可能引起 動(dòng)畫(huà)幀率下降的性能問(wèn)題。在最后一章,我們將著重圖層樹(shù)本身数焊,以發(fā)掘最好的性 能。
隱式繪制
寄宿圖可以通過(guò)Core Graphics
直接繪制佩耳,也可以直接載入一個(gè)圖片文件并賦值給contents
屬性,或事先繪制一個(gè)屏幕之外的 CGContext
上下文干厚。在之前的兩 章中我們討論了這些場(chǎng)景下的優(yōu)化。但是除了常見(jiàn)的顯式創(chuàng)建寄宿圖蛮瞄,你也可以通過(guò)以下三種方式創(chuàng)建隱式的:1,使用特性的圖層屬性挂捅。2芹助,特定的視圖。3闲先,特定 的圖層子類(lèi)状土。
了解這個(gè)情況為什么發(fā)生何時(shí)發(fā)生是很重要的,它能夠讓你避免引入不必要的軟件繪制行為伺糠。
文本
CATextLayer
和 UILabel
都是直接將文本繪制在圖層的寄宿圖中蒙谓。事實(shí)上這兩種方式用了完全不同的渲染方式:在iOS 6
及之前, UILabel
用WebKit
的HTML
渲染引擎來(lái)繪制文本训桶,而 CATextLayer
用的是Core Text
.后者渲染更迅速累驮,所以 在所有需要繪制大量文本的情形下都優(yōu)先使用它吧。但是這兩種方法都用了軟件的 方式繪制渊迁,因此他們實(shí)際上要比硬件加速合成方式要慢慰照。
不論如何,盡可能地避免改變那些包含文本的視圖的frame
琉朽,因?yàn)檫@樣做的話(huà)文本就需要重繪毒租。例如,如果你想在圖層的角落里顯示一段靜態(tài)的文本箱叁,但是這個(gè)圖 層經(jīng)常改動(dòng)墅垮,你就應(yīng)該把文本放在一個(gè)子圖層中。
光柵化
在第四章『視覺(jué)效果』中我們提到了 CALayer
的shouldRasterize
屬性耕漱,它可以解決重疊透明圖層的混合失靈問(wèn)題算色。同樣在第12章『速度的曲調(diào)』
中,它也是作為繪制復(fù)雜圖層樹(shù)結(jié)構(gòu)的優(yōu)化方法螟够。
啟用 shouldRasterize
屬性會(huì)將圖層繪制到一個(gè)屏幕之外的圖像灾梦。然后這個(gè)圖 像將會(huì)被緩存起來(lái)并繪制到實(shí)際圖層的 contents
和子圖層峡钓。如果有很多的子圖層 或者有復(fù)雜的效果應(yīng)用,這樣做就會(huì)比重繪所有事務(wù)的所有幀劃得來(lái)得多若河。但是光 柵化原始圖像需要時(shí)間能岩,而且還會(huì)消耗額外的內(nèi)存。
當(dāng)我們使用得當(dāng)時(shí)萧福,光柵化可以提供很大的性能優(yōu)勢(shì)(如你在第12章所見(jiàn))拉鹃,但 是一定要避免作用在內(nèi)容不斷變動(dòng)的圖層上,否則它緩存方面的好處就會(huì)消失鲫忍,而且會(huì)讓性能變的更糟膏燕。
為了檢測(cè)你是否正確地使用了光柵化方式,用Instrument
查看一下Color Hits Green
和Misses Red
項(xiàng)目悟民,是否已光柵化圖像被頻繁地刷新(這樣就說(shuō)明圖層并不 是光柵化的好選擇,或則你無(wú)意間觸發(fā)了不必要的改變導(dǎo)致了重繪行為)逾雄。
離屏渲染
當(dāng)圖層屬性的混合體被指定為在未預(yù)合成之前不能直接在屏幕中繪制時(shí)鸦泳,屏幕外渲染就被喚起了做鹰。屏幕外渲染并不意味著軟件繪制鼎姐,但是它意味著圖層必須在被顯示之前在一個(gè)屏幕外上下文中被渲染(不論CPU
還是GPU
)。圖層的以下屬性將會(huì) 觸發(fā)屏幕外繪制:
- 圓角(當(dāng)和 maskToBounds 一起使用時(shí))
- 圖層蒙板
- 陰影
屏幕外渲染和我們啟用光柵化時(shí)相似饭尝,除了它并沒(méi)有像光柵化圖層那么消耗大钥平,子圖層并沒(méi)有被影響到姊途,而且結(jié)果也沒(méi)有被緩存捷兰,所以不會(huì)有長(zhǎng)期的內(nèi)存占用。但是秘蛇,如果太多圖層在屏幕外渲染依然會(huì)影響到性能。
有時(shí)候我們可以把那些需要屏幕外繪制的圖層開(kāi)啟光柵化以作為一個(gè)優(yōu)化方式赁还,前提是這些圖層并不會(huì)被頻繁地重繪秽浇。
對(duì)于那些需要?jiǎng)赢?huà)而且要在屏幕外渲染的圖層來(lái)說(shuō),你可以用CAShapeLayer
审残, contentsCenter
或者shadowPath
來(lái)獲得同樣的表現(xiàn)而且 較少地影響到性能搅轿。
CAShapeLayer
cornerRadius
和 maskToBounds
獨(dú)立作用的時(shí)候都不會(huì)有太大的性能問(wèn)題富玷, 但是當(dāng)他倆結(jié)合在一起,就觸發(fā)了屏幕外渲染雀鹃。有時(shí)候你想顯示圓角并沿著圖層裁 切子圖層的時(shí)候黎茎,你可能會(huì)發(fā)現(xiàn)你并不需要沿著圓角裁切当悔,這個(gè)情況下用 CAShapeLayer
就可以避免這個(gè)問(wèn)題了。
你想要的只是圓角且沿著矩形邊界裁切嗅骄,同時(shí)還不希望引起性能問(wèn)題溺森。其實(shí)你可以用現(xiàn)成的UIBezierPath
的構(gòu)造器 + bezierPathWithRoundedRect:cornerRadius:
這樣做并不會(huì)比直接用cornerRadius
更快,但是它避免了性能問(wèn)題宏多。
可伸縮圖片
另一個(gè)創(chuàng)建圓角矩形的方法就是用一個(gè)圓形內(nèi)容圖片并結(jié)合第二章『寄宿圖』提 到的 contensCenter
屬性去創(chuàng)建一個(gè)可伸縮圖片(見(jiàn)清單15.2).理論上來(lái)說(shuō)伸但,這 個(gè)應(yīng)該比用CAShapeLayer
要快,因?yàn)橐粋€(gè)可拉伸圖片只需要18
個(gè)三角形(一個(gè) 圖片是由一個(gè)3*3
網(wǎng)格渲染而成)铛铁,然而,許多都需要渲染成一個(gè)順滑的曲線(xiàn)括眠。在 實(shí)際應(yīng)用上倍权,二者并沒(méi)有太大的區(qū)別。
使用可伸縮圖片的優(yōu)勢(shì)在于它可以繪制成任意邊框效果而不需要額外的性能消耗当船。舉個(gè)例子德频,可伸縮圖片甚至還可以顯示出矩形陰影的效果缩幸。
shadowPath
在第2
章我們有提到 shadowPath
屬性。如果圖層是一個(gè)簡(jiǎn)單幾何圖形如矩形或 者圓角矩形(假設(shè)不包含任何透明部分或者子圖層)钞护,創(chuàng)建出一個(gè)對(duì)應(yīng)形狀的陰影 路徑就比較容易患亿,而且Core Animation
繪制這個(gè)陰影也相當(dāng)簡(jiǎn)單押逼,避免了屏幕外的 圖層部分的預(yù)排版需求挑格。這對(duì)性能來(lái)說(shuō)很有幫助沾歪。
如果你的圖層是一個(gè)更復(fù)雜的圖形,生成正確的陰影路徑可能就比較難了挫望,這樣子的話(huà)你可以考慮用繪圖軟件預(yù)先生成一個(gè)陰影背景圖媳板。
混合和過(guò)度繪制
在第12
章有提到泉哈,GPU
每一幀可以繪制的像素有一個(gè)最大限制(就是所謂的fill rate
),這個(gè)情況下可以輕易地繪制整個(gè)屏幕的所有像素奕纫。但是如果由于重疊圖層的關(guān)系需要不停地重繪同一區(qū)域的話(huà),掉幀就可能發(fā)生了隙笆。
GPU
會(huì)放棄繪制那些完全被其他圖層遮擋的像素撑柔,但是要計(jì)算出一個(gè)圖層是否被 遮擋也是相當(dāng)復(fù)雜并且會(huì)消耗處理器資源仰冠。同樣,合并不同圖層的透明重疊像素 (即混合)消耗的資源也是相當(dāng)客觀的辆沦。所以為了加速處理進(jìn)程肢扯,不到必須時(shí)刻不 要使用透明圖層担锤。任何情況下,你應(yīng)該這樣做:
- 給視圖的 屬性設(shè)置一個(gè)固定的铭腕,不透明的顏色
- 設(shè)置
opaque
屬性為YES
這樣做減少了混合行為(因?yàn)榫幾g器知道在圖層之后的東西都不會(huì)對(duì)最終的像素 顏色產(chǎn)生影響)并且計(jì)算得到了加速累舷,避免了過(guò)度繪制行為因?yàn)?code>Core Animation可以舍棄所有被完全遮蓋住的圖層夹孔,而不用每個(gè)像素都去計(jì)算一遍。
如果用到了圖像只怎,盡量避免透明除非非常必要怜俐。如果圖像要顯示在一個(gè)固定的背景顏色或是固定的背景圖之前佑菩,你沒(méi)必要相對(duì)前景移動(dòng)裁赠,你只需要預(yù)填充背景圖片就可以避免運(yùn)行時(shí)混色了佩捞。
如果是文本的話(huà)蕾哟,一個(gè)白色背景的 UILabel
(或者其他顏色)會(huì)比透明背景要 更高效谭确。
最后,明智地使用 shouldRasterize
屬性芬迄,可以將一個(gè)固定的圖層體系折疊成 單張圖片昂秃,這樣就不需要每一幀重新合成了,也就不會(huì)有因?yàn)樽訄D層之間的混合和過(guò)度繪制的性能問(wèn)題了算途。
減少圖層數(shù)量
初始化圖層嘴瓤,處理圖層莉钙,打包通過(guò)IPC
發(fā)給渲染引擎,轉(zhuǎn)化成OpenGL
幾何圖 形狞贱,這些是一個(gè)圖層的大致資源開(kāi)銷(xiāo)。事實(shí)上蝎毡,一次性能夠在屏幕上顯示的最大圖 層數(shù)量也是有限的。
確切的限制數(shù)量取決于iOS
設(shè)備别垮,圖層類(lèi)型扎谎,圖層內(nèi)容和屬性等。但是總得說(shuō)來(lái) 可以容納上百或上千個(gè)胧奔,下面我們將演示即使圖層本身并沒(méi)有做什么也會(huì)遇到的性 能問(wèn)題。
裁切
在對(duì)圖層做任何優(yōu)化之前胳泉,你需要確定你不是在創(chuàng)建一些不可見(jiàn)的圖層扇商,圖層在以下幾種情況下回事不可見(jiàn)的:
- 圖層在屏幕邊界之外,或是在父圖層邊界之外案铺。
- 完全在一個(gè)不透明圖層之后控汉。
- 完全透明
Core Animation
非常擅長(zhǎng)處理對(duì)視覺(jué)效果無(wú)意義的圖層涤姊。但是經(jīng)常性地,你自己 的代碼會(huì)比Core Animation更早地想知道一個(gè)圖層是否是有用的壁酬。理想狀況下舆乔,在圖層對(duì)象在創(chuàng)建之前就想知道剂公,以避免創(chuàng)建和配置不必要圖層的額外工作。
對(duì)象回收
處理巨大數(shù)量的相似視圖或圖層時(shí)還有一個(gè)技巧就是回收他們颜武。對(duì)象回收在iOS 頗為常見(jiàn); UITableView
和UICollectionView
都有用到鳞上, MKMapView
中的動(dòng)畫(huà)pin
碼也有用到吊档,還有其他很多例子。
對(duì)象回收的基礎(chǔ)原則就是你需要?jiǎng)?chuàng)建一個(gè)相似對(duì)象池鬼贱。當(dāng)一個(gè)對(duì)象的指定實(shí)例(本例子中指的是圖層)結(jié)束了使命这难,你把它添加到對(duì)象池中。每次當(dāng)你需要一個(gè)實(shí)例時(shí)脐帝,你就從池中取出一個(gè)糖权。當(dāng)且僅當(dāng)池中為空時(shí)再創(chuàng)建一個(gè)新的星澳。
這樣做的好處在于避免了不斷創(chuàng)建和釋放對(duì)象(相當(dāng)消耗資源,因?yàn)樯婕暗絻?nèi)存的分配和銷(xiāo)毀)而且也不必給相似實(shí)例重復(fù)賦值腿堤。
Core Graphics繪制
當(dāng)排除掉對(duì)屏幕顯示沒(méi)有任何貢獻(xiàn)的圖層或者視圖之后如暖,長(zhǎng)遠(yuǎn)看來(lái)盒至,你可能仍然 需要減少圖層的數(shù)量。例如樱衷,如果你正在使用多個(gè)UILabel
或者UIImageView
實(shí)例去顯示固定內(nèi)容酒唉,你可以把他們?nèi)刻鎿Q成一個(gè)單獨(dú)的視 圖,然后用- drawRect:
方法繪制出那些復(fù)雜的視圖層級(jí)侄榴。
這個(gè)提議看上去并不合理因?yàn)榇蠹叶贾儡浖L制行為要比GPU
合成要慢而且還 需要更多的內(nèi)存空間网沾,但是在因?yàn)閳D層數(shù)量而使得性能受限的情況下,軟件繪制很 可能提高性能呢,因?yàn)樗苊饬藞D層分配和操作問(wèn)題证薇。
你可以自己實(shí)驗(yàn)一下這個(gè)情況,它包含了性能和柵格化的權(quán)衡浑度,但是意味著你可 以從圖層樹(shù)上去掉子圖層(用shouldRasterize
,與完全遮擋圖層相反)甩骏。
-renderInContext: 方法
用Core Graphics
去繪制一個(gè)靜態(tài)布局有時(shí)候會(huì)比用層級(jí)的 UIView
實(shí)例來(lái)得快饮笛,但是使用UIView
實(shí)例要簡(jiǎn)單得多而且比用手寫(xiě)代碼寫(xiě)出相同效果要可靠得 多论熙,更邊說(shuō)Interface Builder
來(lái)得直接明了。為了性能而舍棄這些便利實(shí)在是不應(yīng) 該脓诡。
幸好祝谚,你不必這樣,如果大量的視圖或者圖層真的關(guān)聯(lián)到了屏幕上將會(huì)是一個(gè)大問(wèn)題交惯。沒(méi)有與圖層樹(shù)相關(guān)聯(lián)的圖層不會(huì)被送到渲染引擎商玫,也沒(méi)有性能問(wèn)題(在他們被創(chuàng)建和配置之后)。
使用 CALayer
的 -renderInContext:
方法袭异,你可以將圖層及其子圖層快照進(jìn) 一個(gè)Core Graphics
上下文然后得到一個(gè)圖片炬藤,它可以直接顯示在UIImageView
中,或者作為另一個(gè)圖層的contents
上真。不同于shouldRasterize
—— 要求圖層與圖層樹(shù)相關(guān)聯(lián) —— 睡互,這個(gè)方法沒(méi)有持續(xù)的 性能消耗。
當(dāng)圖層內(nèi)容改變時(shí)就珠,刷新這張圖片的機(jī)會(huì)取決于你(不同于 shouldRasterize
妻怎,它自動(dòng)地處理緩存和緩存驗(yàn)證),但是一旦圖片被生成匿辩, 相比于讓Core Animation
處理一個(gè)復(fù)雜的圖層樹(shù)榛丢,你節(jié)省了相當(dāng)客觀的性能。
總結(jié)
本章學(xué)習(xí)了使用Core Animation
圖層可能遇到的性能瓶頸睬辐,并討論了如何避免或 減小壓力溯饵。你學(xué)習(xí)了如何管理包含上千虛擬圖層的場(chǎng)景(事實(shí)上只創(chuàng)建了幾百 個(gè))锨用。同時(shí)也學(xué)習(xí)了一些有用的技巧,選擇性地選取光柵化或者繪制圖層內(nèi)容在合 適的時(shí)候重新分配給CPU
和GPU
啄巧。這些就是我們要講的關(guān)于Core Animation
的全部 了(至少可以等到蘋(píng)果發(fā)明什么新的玩意兒)掌栅。