關(guān)于drawRect內(nèi)存暴增

關(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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旁仿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子孽糖,更是在濱河造成了極大的恐慌枯冈,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件办悟,死亡現(xiàn)場(chǎng)離奇詭異尘奏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)病蛉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門炫加,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瑰煎,“玉大人,你說(shuō)我怎么就攤上這事俗孝【频椋” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵赋铝,是天一觀的道長(zhǎng)插勤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)柬甥,這世上最難降的妖魔是什么饮六? 我笑而不...
    開封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮苛蒲,結(jié)果婚禮上卤橄,老公的妹妹穿的比我還像新娘。我一直安慰自己臂外,他們只是感情好窟扑,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著漏健,像睡著了一般嚎货。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蔫浆,一...
    開封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天殖属,我揣著相機(jī)與錄音,去河邊找鬼瓦盛。 笑死洗显,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的原环。 我是一名探鬼主播挠唆,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嘱吗!你這毒婦竟也來(lái)了玄组?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤谒麦,失蹤者是張志新(化名)和其女友劉穎俄讹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弄匕,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颅悉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了迁匠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖城丧,靈堂內(nèi)的尸體忽然破棺而出延曙,到底是詐尸還是另有隱情,我是刑警寧澤亡哄,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布枝缔,位于F島的核電站,受9級(jí)特大地震影響蚊惯,放射性物質(zhì)發(fā)生泄漏愿卸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一截型、第九天 我趴在偏房一處隱蔽的房頂上張望趴荸。 院中可真熱鬧,春花似錦宦焦、人聲如沸发钝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)酝豪。三九已至,卻和暖如春精堕,著一層夾襖步出監(jiān)牢的瞬間孵淘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工歹篓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瘫证,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓滋捶,卻偏偏與公主長(zhǎng)得像痛悯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子重窟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容