關(guān)于drawRect內(nèi)存暴增的總結(jié)蜓耻,在新建的view里重寫了- (void)drawRect:(CGRect)rect ;
- (void)drawRect:(CGRect)rect {
// Drawing code
}
然后在控制器里創(chuàng)建了這個(gè)view
//這里設(shè)置的size為5倍亮隙,是為了放大內(nèi)存暴增的效果
DrawView * drawView =[[DrawView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width * 1, self.view.frame.size.height * 1)];
drawView.backgroundColor=[UIColor purpleColor];
[self.view addSubview:drawView];
沒有重寫- (void)drawRect:(CGRect)rect方法時(shí),只要你創(chuàng)建view税稼,那么模擬器7p上測(cè)試的內(nèi)存大概21mb
重寫了- (void)drawRect:(CGRect)rect方法時(shí),只要你創(chuàng)建view,那么內(nèi)存會(huì)從21mb暴增到283mb
我們終于抓到了消耗內(nèi)存的惡鬼针姿,問(wèn)題就出在對(duì)drawRect方法的覆蓋
那么現(xiàn)在我們分析一下drawRect導(dǎo)致內(nèi)存暴增的真正原因:
重寫drawRect為何會(huì)導(dǎo)致內(nèi)存大量上漲窗声?
要想搞明白這個(gè)問(wèn)題相恃,我們需要擼一擼在iOS程序上圖形顯示的原理。在iOS系統(tǒng)中所有顯示的視圖都是從基類UIView繼承而來(lái)的笨觅,同時(shí)UIView負(fù)責(zé)接收用戶交互拦耐。但是實(shí)際上你所看到的視圖內(nèi)容耕腾,包括圖形等,都是由UIView的一個(gè)實(shí)例圖層屬性來(lái)繪制和渲染的杀糯,那就是CALayer扫俺。
CALayer類的概念與UIView非常類似,它也具有樹形的層級(jí)關(guān)系固翰,并且可以包含圖片文本狼纬、背景色等。它與UIView最大的不同在于它不能響應(yīng)用戶交互骂际,可以說(shuō)它根本就不知道響應(yīng)鏈的存在畸颅,它的API雖然提供了“某點(diǎn)是否在圖層范圍內(nèi)的方法”,但是它并不具有響應(yīng)的能力方援。
在每一個(gè)UIView實(shí)例當(dāng)中没炒,都有一個(gè)默認(rèn)的支持圖層,UIView負(fù)責(zé)創(chuàng)建并且管理這個(gè)圖層犯戏。實(shí)際上這個(gè)CALayer圖層才是真正用來(lái)在屏幕上顯示的送火,UIView僅僅是對(duì)它的一層封裝,實(shí)現(xiàn)了CALayer的delegate先匪,提供了處理事件交互的具體功能种吸,還有動(dòng)畫底層方法的高級(jí)API。
可以說(shuō)CALayer是UIView的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)呀非。
腦補(bǔ)了這么多坚俗,它與今天的主題drawRect有何關(guān)系呢?別著急岸裙,我們既然已經(jīng)確定CALayer才是最終顯示到屏幕上的猖败,只要順藤摸瓜,即可分析清楚降允。CALayer其實(shí)也只是iOS當(dāng)中一個(gè)普通的類恩闻,它也并不能直接渲染到屏幕上,因?yàn)槠聊簧夏闼吹降臇|西剧董,其實(shí)都是一張張圖片幢尚。而為什么我們能看到CALayer的內(nèi)容呢,是因?yàn)镃ALayer內(nèi)部有一個(gè)contents屬性翅楼。contents默認(rèn)可以傳一個(gè)id類型的對(duì)象尉剩,但是只有你傳CGImage的時(shí)候,它才能夠正常顯示在屏幕上毅臊。所以最終我們的圖形渲染落點(diǎn)落在contents身上
contents也被稱為寄宿圖理茎,除了給它賦值CGImage之外,我們也可以直接對(duì)它進(jìn)行繪制,繪制的方法正是這次問(wèn)題的關(guān)鍵功蜓,通過(guò)繼承UIView并實(shí)現(xiàn)-drawRect:方法即可自定義繪制园爷。-drawRect: 方法沒有默認(rèn)的實(shí)現(xiàn),因?yàn)閷?duì)UIView來(lái)說(shuō)式撼,寄宿圖并不是必須的童社,UIView不關(guān)心繪制的內(nèi)容。如果UIView檢測(cè)到-drawRect:方法被調(diào)用了著隆,它就會(huì)為視圖分配一個(gè)寄宿圖扰楼,這個(gè)寄宿圖的像素尺寸等于視圖大小乘以contentsScale(這個(gè)屬性與屏幕分辨率有關(guān),我們的畫板程序在不同模擬器下呈現(xiàn)的內(nèi)存用量不同也是因?yàn)樗?的值美浦。
那么回到我們的畫板程序弦赖,當(dāng)畫板從屏幕上出現(xiàn)的時(shí)候,因?yàn)橹貙懥?drawRect:方法浦辨,-drawRect :方法就會(huì)自動(dòng)調(diào)用蹬竖。生成一張寄宿圖后,方法里面的代碼利用Core Graphics去繪制n條黑色的線流酬,然后內(nèi)容就會(huì)緩存起來(lái)币厕,等待下次你調(diào)用-setNeedsDisplay時(shí)再進(jìn)行更新。
畫板視圖的-drawRect:方法的背后實(shí)際上都是底層的CALayer進(jìn)行了重繪和保存中間產(chǎn)生的圖片芽腾,CALayer的delegate屬性默認(rèn)實(shí)現(xiàn)了CALayerDelegate協(xié)議旦装,當(dāng)它需要內(nèi)容信息的時(shí)候會(huì)調(diào)用協(xié)議中的方法來(lái)拿。當(dāng)畫板視圖重繪時(shí)摊滔,因?yàn)樗闹С謭D層CALayer的代理就是畫板視圖本身阴绢,所以支持圖層會(huì)請(qǐng)求畫板視圖給它一個(gè)寄宿圖來(lái)顯示,它此刻會(huì)調(diào)用:
- (void)displayLayer:(CALayer *)layer;
如果畫板視圖實(shí)現(xiàn)了這個(gè)方法艰躺,就可以拿到layer來(lái)直接設(shè)置contents寄宿圖呻袭,如果這個(gè)方法沒有實(shí)現(xiàn),支持圖層CALayer會(huì)嘗試調(diào)用
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
這個(gè)方法調(diào)用之前描滔,CALayer創(chuàng)建了一個(gè)合適尺寸的空寄宿圖(尺寸由bounds和contentsScale決定)和一個(gè)Core Graphics的繪制上下文環(huán)境棒妨,為繪制寄宿圖做準(zhǔn)備,它作為ctx參數(shù)傳入含长。在這一步生成的空寄宿圖內(nèi)存是相當(dāng)巨大的,它就是本次內(nèi)存問(wèn)題的關(guān)鍵伏穆,一旦你實(shí)現(xiàn)了CALayerDelegate協(xié)議中的-drawLayer:inContext:方法或者UIView中的-drawRect:方法(其實(shí)就是前者的包裝方法)拘泞,圖層就創(chuàng)建了一個(gè)繪制上下文,這個(gè)上下文需要的內(nèi)存可從這個(gè)公式得出:圖層寬圖層高4字節(jié)枕扫,寬高的單位均為像素陪腌。而我們的畫板程序因?yàn)橐С窒裨愁}庫(kù)一樣兩指挪動(dòng)的效果,我們開辟的畫板大小為
DrawView * drawView =[[DrawView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width * 1, self.view.frame.size.height * 1)];
我們的畫板程序的畫板視圖它在iPhone6s plus機(jī)器上的上下文內(nèi)存量就是 19205108054字節(jié),相當(dāng)于79MB內(nèi)存诗鸭,圖層每次重繪的時(shí)候都需要重新抹掉內(nèi)存然后重新分配染簇。它就是我們畫板程序內(nèi)存暴增的真正原因岩榆。
最終我們將內(nèi)存暴增的原因找出來(lái)了蚤氏,那么我們有沒有合理的解決方案呢?
我認(rèn)為最合理的辦法處理類似于畫板這樣畫線條的需求直接用專有圖層CAShapeLayer蔫骂。讓我們看看它是什么:
CAShapeLayer是一個(gè)通過(guò)矢量圖形而不是bitmap來(lái)繪制的圖層子類蝌箍。用CGPath來(lái)定義想要繪制的圖形青灼,CAShapeLayer會(huì)自動(dòng)渲染。它可以完美替代我們的直接使用Core Graphics繪制layer妓盲,對(duì)比之下使用CAShapeLayer有以下優(yōu)點(diǎn):
渲染快速杂拨。CAShapeLayer使用了硬件加速,繪制同一圖形會(huì)比用Core Graphics快很多悯衬。
高效使用內(nèi)存弹沽。一個(gè)CAShapeLayer不需要像普通CALayer一樣創(chuàng)建一個(gè)寄宿圖形,所以無(wú)論有多大筋粗,都不會(huì)占用太多的內(nèi)存贷币。
不會(huì)被圖層邊界剪裁掉。
不會(huì)出現(xiàn)像素化亏狰。
以下方法調(diào)用drawRect:
1.如果在UIView初始化時(shí)沒有設(shè)置rect大小役纹,將直接導(dǎo)致drawRect不被自動(dòng)調(diào)用。
2.該方法在調(diào)用sizeThatFits后被調(diào)用暇唾,所以可以先調(diào)用sizeToFit計(jì)算出size促脉。然后系統(tǒng)自動(dòng)調(diào)用drawRect:方法。
3.通過(guò)設(shè)置contentMode屬性值為UIViewContentModeRedraw策州。那么將在每次設(shè)置或更改frame的時(shí)候自動(dòng)調(diào)用drawRect:瘸味。
4.直接調(diào)用setNeedsDisplay,或者setNeedsDisplayInRect:觸發(fā)drawRect:够挂,但是有個(gè)前提條件是rect不能為0.
轉(zhuǎn)載出處:http://bihongbo.com/2016/01/03/memoryGhostdrawRect/