久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

內(nèi)存惡鬼drawRect | Hongbo Bi’s Blog

 宇智波瞬潤 2016-05-10

標題有點嚇人,但是對于drawRect的評價倒是一點都不過分,。在平日的開發(fā)中,,隨意覆蓋drawRect方法,稍有不慎就會讓你的程序內(nèi)存暴增,。下面我們來看一個例子,。

去年的某天午后,北京的霧霾依舊像現(xiàn)在這樣醇厚,,我的同事輝哥像往常一樣與我樓下約煙,。我見輝哥表情凝重,便詢問究竟,。輝哥做了一個畫板功能,,但是苦于內(nèi)存問題一直得不到解決。畫板功能很簡單,,就是記錄手指觸摸的軌跡然后繪制在屏幕上,。下面我們來看一張效果圖:

效果圖效果圖

如圖我們看到左側(cè)內(nèi)存的狀況隨著手指的繪制逐漸惡化。另外細心的同學可以觀察到,,點擊圖中藍色矩形按鈕之后,,便會彈出畫板,而這時并沒有進行任何的手指繪制,,內(nèi)存就突變?yōu)?code>114MB,,然后每當手指繪制開始時,內(nèi)存立即增加到300MB左右穩(wěn)定下來。對于正常的iOS App來講,,這么大的內(nèi)存消耗是不能容忍的,。

下面分析一下原因:
可能的原因有兩個,一是在手指繪制的過程中創(chuàng)建的大量點對象沒有及時釋放或者其他資源沒有及時釋放,。
二是系統(tǒng)在繪制的過程中開始大量消耗內(nèi)存,。

第一個原因,手指繪制的過程中創(chuàng)建的大量點對象沒有及時釋放或者其他資源沒有及時釋放,。這一點我們暫時排除以節(jié)省時間,,因為這個畫板功能工程是用ARC寫的,并且我們已經(jīng)做過代碼檢查和使用Instruments工具來檢測內(nèi)存使用情況,,這里并沒有所謂的對象沒有及時釋放的問題存在,。

第二個原因,系統(tǒng)在繪制的過程中開始大量消耗內(nèi)存,。首先我們曾經(jīng)注意到一個詭異并且不尋常的事情就是,,當黃色的畫板剛剛彈出的時候內(nèi)存就瞬間從18MB暴增至114MB。這一點更加說明第一個原因不是問題所在,,因為這時手指還沒有進行任何繪制,,也就是說不存在任何點與線的對象,那么內(nèi)存怎么會暴增呢,?

這時我們要考慮這個畫板功能是如何實現(xiàn)的,,畫板分為兩步,第一步記錄用戶手指的軌跡,,這一步會生成大量點的對象(已排除嫌疑),。第二步繪制到視圖或者圖層上,我們平常使用頻繁的繪圖方式基本上是Quarz2D的那套C語言框架,,而繪制代碼所在的地點在哪呢,?我們今天的主角終于上場了--drawRect

下面我們來看一段畫板功能繪制的代碼:

1
2
3
4
5
6
7
8
9
10
11
- (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);
}
}

去掉繪圖上下文棧和其余判斷邊界的代碼,,我們只是在當前view上繪制了n條黑色的線,。看起來普普通通的繪圖方式,,怎么會導(dǎo)致內(nèi)存的劇增呢,?我們現(xiàn)在說罪魁禍首是drawRect證據(jù)并不充分。我們回想畫板剛彈出時的內(nèi)存狀況,,接下來我們注釋掉drawRect所有的代碼,。運行的效果圖如下:

效果圖1效果圖1

效果立竿見影,注釋掉drawRect之后,,內(nèi)存立刻恢復(fù)正常,,我們終于抓到了消耗內(nèi)存的惡鬼,,問題就出在對drawRect方法的覆蓋。那么抓到了犯人,,本文是否應(yīng)該完結(jié)了,?非也非也,我們雖說知道了內(nèi)存暴增的原因,,但是我們并沒有深入的去分析drawRect為什么對內(nèi)存的影響這么大,,而且我們也沒有給出問題的解決方案。請接著往下看,。

那么現(xiàn)在我們分析一下drawRect導(dǎo)致內(nèi)存暴增的真正原因:

重寫drawRect為何會導(dǎo)致內(nèi)存大量上漲,?

要想搞明白這個問題,我們需要擼一擼在iOS程序上圖形顯示的原理,。在iOS系統(tǒng)中所有顯示的視圖都是從基類UIView繼承而來的,,同時UIView負責接收用戶交互。但是實際上你所看到的視圖內(nèi)容,,包括圖形等,,都是由UIView的一個實例圖層屬性來繪制和渲染的,那就是CALayer,。

CALayer類的概念與UIView非常類似,,它也具有樹形的層級關(guān)系,并且可以包含圖片文本,、背景色等。它與UIView最大的不同在于它不能響應(yīng)用戶交互,,可以說它根本就不知道響應(yīng)鏈的存在,,它的API雖然提供了“某點是否在圖層范圍內(nèi)的方法”,但是它并不具有響應(yīng)的能力,。

在每一個UIView實例當中,,都有一個默認的支持圖層,UIView負責創(chuàng)建并且管理這個圖層,。實際上這個CALayer圖層才是真正用來在屏幕上顯示的,,UIView僅僅是對它的一層封裝,實現(xiàn)了CALayerdelegate,,提供了處理事件交互的具體功能,,還有動畫底層方法的高級API。

可以說CALayerUIView的內(nèi)部實現(xiàn)細節(jié),。

腦補了這么多,,它與今天的主題drawRect有何關(guān)系呢?別著急,,我們既然已經(jīng)確定CALayer才是最終顯示到屏幕上的,,只要順藤摸瓜,,即可分析清楚。CALayer其實也只是iOS當中一個普通的類,,它也并不能直接渲染到屏幕上,,因為屏幕上你所看到的東西,其實都是一張張圖片,。而為什么我們能看到CALayer的內(nèi)容呢,,是因為CALayer內(nèi)部有一個contents屬性。contents默認可以傳一個id類型的對象,,但是只有你傳CGImage的時候,,它才能夠正常顯示在屏幕上。所以最終我們的圖形渲染落點落在contents身上如圖,。

效果圖2效果圖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)存用量不同也是因為它)的值,。

那么回到我們的畫板程序,當畫板從屏幕上出現(xiàn)的時候,,因為重寫了-drawRect:方法,,-drawRect :方法就會自動調(diào)用。生成一張寄宿圖后,,方法里面的代碼利用Core Graphics去繪制n條黑色的線,,然后內(nèi)容就會緩存起來,等待下次你調(diào)用-setNeedsDisplay時再進行更新,。

畫板視圖的-drawRect:方法的背后實際上都是底層的CALayer進行了重繪和保存中間產(chǎn)生的圖片,,CALayerdelegate屬性默認實現(xiàn)了CALayerDelegate協(xié)議,當它需要內(nèi)容信息的時候會調(diào)用協(xié)議中的方法來拿,。當畫板視圖重繪時,,因為它的支持圖層CALayer的代理就是畫板視圖本身,所以支持圖層會請求畫板視圖給它一個寄宿圖來顯示,,它此刻會調(diào)用:

1
- (void)displayLayer:(CALayer *)layer;

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

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

這個方法調(diào)用之前,,CALayer創(chuàng)建了一個合適尺寸的空寄宿圖(尺寸由boundscontentsScale決定)和一個Core Graphics的繪制上下文環(huán)境,,為繪制寄宿圖做準備,它作為ctx參數(shù)傳入,。在這一步生成的空寄宿圖內(nèi)存是相當巨大的,,它就是本次內(nèi)存問題的關(guān)鍵,一旦你實現(xiàn)了CALayerDelegate協(xié)議中的-drawLayer:inContext:方法或者UIView中的-drawRect:方法(其實就是前者的包裝方法),,圖層就創(chuàng)建了一個繪制上下文,,這個上下文需要的內(nèi)存可從這個公式得出:圖層寬*圖層高*4字節(jié),寬高的單位均為像素,。而我們的畫板程序因為要支持像猿題庫一樣兩指挪動的效果,,我們開辟的畫板大小為:

1
_myDrawer = [[BHBMyDrawer alloc] initWithFrame:CGRectMake(0, 0, SCREEN_SIZE.width*5, SCREEN_SIZE.height*2)];

我們的畫板程序的畫板視圖它在iPhone6s plus機器上的上下文內(nèi)存量就是 1920*2*1080*5*4字節(jié)相當于79MB內(nèi)存,,圖層每次重繪的時候都需要重新抹掉內(nèi)存然后重新分配,。它就是我們畫板程序內(nèi)存暴增的真正原因。

最終我們將內(nèi)存暴增的原因找出來了,,那么我們有沒有合理的解決方案呢,?

我認為最合理的辦法處理類似于畫板這樣畫線條的需求直接用專有圖層CAShapeLayer。讓我們看看它是什么:

CAShapeLayer是一個通過矢量圖形而不是bitmap來繪制的圖層子類,。用CGPath來定義想要繪制的圖形,,CAShapeLayer會自動渲染。它可以完美替代我們的直接使用Core Graphics繪制layer,,對比之下使用CAShapeLayer有以下優(yōu)點:

  • 渲染快速,。CAShapeLayer使用了硬件加速,繪制同一圖形會比用Core Graphics快很多,。
  • 高效使用內(nèi)存。一個CAShapeLayer不需要像普通CALayer一樣創(chuàng)建一個寄宿圖形,,所以無論有多大,,都不會占用太多的內(nèi)存。
  • 不會被圖層邊界剪裁掉,。
  • 不會出現(xiàn)像素化,。

所以最終我們的畫板程序使用CAShapeLayer來實現(xiàn)線條的繪制,性能非常穩(wěn)定,,效果圖如下:

效果圖3效果圖3

總結(jié)一下繪制性能優(yōu)化原則:

  • 1.繪制圖形性能的優(yōu)化最好的辦法就是不去繪制,。
  • 2.利用專有圖層代替繪圖需求。
  • 3.不得不用到繪圖盡量縮小視圖面積,,并且盡量降低重繪頻率,。
  • 4.異步繪制,,推測內(nèi)容,提前在其他線程繪制圖片,,在主線程中直接設(shè)置圖片,。

本文最后一個效果圖為仿寫猿題庫練題畫板功能,demo請在github搜索BHBDrawBoarderDemo,?;蛘咧苯?a target="_blank" rel="external">戳這里。

好了,,就是這么多,,文章比較亂,如有紕漏請不吝指出,!想看對本文的補充請到這里

good luck,!

參考:

iOS Core Animation: Advanced Techniques

感謝:

AttackOnDobby及其翻譯團隊。

---------------

版權(quán)申明:我

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多