如何解決CALayer的contents內(nèi)存暴增問題

前兩天QQ群大佬提出一個問題,如何解決CALayer的contents屬性賦值之后的內(nèi)存暴增問題,百度了一下無頭緒,偶然看到了唐巧的文章,感覺很有意思,特此記錄一下。原文在此:內(nèi)存惡鬼drawRect - 談畫圖功能的內(nèi)存優(yōu)化

正文:

先來說一下contens是個啥東西:CoreAnimation:CALayer的contents
唐巧文章里寫的很清楚了,在這里不再過多的贅述,大概是這樣的:

  1. 搞了一個簡易功能的畫板,記錄手指觸摸的軌跡然后繪制在屏幕上。
  2. 發(fā)現(xiàn)兩個問題:當(dāng)畫板彈出,其余無任何操作時,內(nèi)存激增,然后當(dāng)手指繪制開始時,內(nèi)存又激增。
  3. 分析原因可能有兩個:一是在手指繪制的過程中創(chuàng)建的大量點對象沒有及時釋放或者其他資源沒有及時釋放。這一點因為工程為ARCInstruments工具得以排除。二是系統(tǒng)在繪制過程中開始大量消耗內(nèi)存,但是明顯可以發(fā)現(xiàn)是畫板創(chuàng)建之后就會有內(nèi)存激增,所以矛頭直指drawRect。
    這是畫板繪制功能的一段代碼:
- (void)drawRect:(CGRect)rect
{
    if (!self.paths.count) return;
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    for (BHBPaintPath *path in self.paths) {
      CGContextSaveGState(ctx);
      [[UIColor blackColor] set];
      [path stroke]; // 關(guān)鍵的一步繪制
      CGContextRestoreGState(ctx);
    }
}
  1. 分析為何drawRect會出現(xiàn)內(nèi)存暴增的真正原因:
    簡單來說,咱們所看到的屏幕上的東西,其實都是一張圖片,是由CALayercontents屬性掌管著,最終圖形渲染落點落在了contents身上。
  2. contents也被稱為寄宿圖,除了給它賦值CGImage以外,我們也可以直接對它進行繪制,繪制的方法正式此次問題的關(guān)鍵,通過集成UIView并實現(xiàn)-drawRect:方法即可自定義繪制。-drawRect:方法沒有默認的實現(xiàn),因此對UIView來說,寄宿圖并不是必須的,UIView不關(guān)心繪制的內(nèi)容。如果UIView檢測到-drawRect:方法被調(diào)用了,他就會為視圖分配一個寄宿圖,這個寄宿圖的像素尺寸等于視圖大小乘以contentsScale(這個屬性與屏幕分辨率有關(guān),我們的畫板程序在不同模擬器下呈現(xiàn)的內(nèi)存用量不同也是因為它)的值。
    那么我們重回畫板程序,當(dāng)畫板從屏幕上出現(xiàn)的時候,因為重寫了-drawRect:方法,-drawRect:方法就會自動調(diào)用。生成一張寄宿圖后,方法里面的代碼利用Core Graphics去繪制n條黑色的線,然后 內(nèi)容就會緩存起來,等待下次你調(diào)用-setNeedsDisplay時再進行更新。
    畫板視圖的-drawRect:方法的背后實際上都是底層的CALayer進行了重繪和保存中間產(chǎn)生的圖片,CALayerdelegate屬性默認實現(xiàn)了CALyaerDelegate協(xié)議,當(dāng)它需要內(nèi)容信息的時候回調(diào)用協(xié)議中的方法來拿。當(dāng)畫板重繪時,因為他的支持圖層CALayer的代理就是畫板視圖的本身,所以支持圖層會請求畫板視圖給它一個寄宿圖來顯示,它此刻會調(diào)用:
- (void)displayLayer:(CALayer *)layer;

如果畫板試圖實現(xiàn)了這個方法,就可以拿到layer來直接設(shè)置contents寄宿圖,如果這個方法沒有實現(xiàn),支持圖層CALayer會嘗試調(diào)用:

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;

這個方法調(diào)用之前,CALayer創(chuàng)建了一個合適尺寸的空寄宿圖(尺寸由boundscontentsScale決定)和一個Core Graphics的繪制山下文環(huán)境,為繪制寄宿圖做準備,它作為ctx參數(shù)傳入。在這一步生成的空寄宿圖內(nèi)存是相當(dāng)巨大的,它就是本次內(nèi)存問題的關(guān)鍵,一旦你實現(xiàn)了CALayerDelegate協(xié)議中的-drawLayer:inContext:方法或者UIView中的-drawRect:方法(其實就是前者的包裝方法),圖層就創(chuàng)建了一個繪制上下文,這個上下文需要的內(nèi)存可從這個公式得出:圖層寬圖層高4字節(jié),寬高的單位均為像素。所以圖層在每次繪制的時候都需要重新抹掉內(nèi)存然后重新分配。它就是我們畫板程序內(nèi)存暴增的真正原因。

  1. 解決方法
    處理類似于畫板這樣畫線條的需求直接用專用圖層CAShapeLayer。
    來看看這是個什么東西:
    CAShapeLayer是一個通過矢量圖形而不是bitmap來繪制的圖層子類。用CGPath來定義想要繪制的圖形,CAShapeLayer會自動渲染。他可以完美替代我們直接使用Core Graphics繪制layer,相比之下使用CAShapeLayer有以下優(yōu)點:
  • 渲染快速。CAShapeLayer使用了硬件加速,繪制同一圖形會比用Core Graphics快很多。
  • 高效使用內(nèi)存。一個CAShapeLayer不需要像普通CALayer一樣創(chuàng)建一個寄宿圖形,所以無論有多大,都不會占用太多的內(nèi)存。
  • 不會被圖形邊界剪裁掉。
  • 不會出現(xiàn)像素化。
  1. 總結(jié)一下繪制性能優(yōu)化原則:
  • 繪制圖形性能的優(yōu)化最好的辦法就是不去繪制。
  • 利用專有圖層代替繪圖需求。
  • 不得不用到的繪圖盡量縮小試圖面積,并且盡量降低重繪頻率。
  • 異步繪制,推測內(nèi)容,提前在其他線程繪制圖片,在主線程中直接設(shè)置圖片。
  1. 最后附上畫板-demo
?著作權(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)容

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