近段一個(gè)新的需求是要做一個(gè)雷達(dá)圖用來展示數(shù)據(jù)硕勿,下圖為兩種方式繪制的雷達(dá)圖以及內(nèi)存使用情況:
在調(diào)研過后 發(fā)現(xiàn)現(xiàn)在市面上有兩種做雷達(dá)圖的方式一種是通過 drawRect
進(jìn)行繪制 另一種則是通過 CAShapeLayer
去繪制幌绍,那么既然有兩種繪制方式的話, 那么就來對(duì)比一下這種兩種繪制方式在內(nèi)存方面的的優(yōu)與劣,如上圖,drawRect 方式繪制相同的雷達(dá)圖占用的內(nèi)存比CAShapeLayer 整整大了8M概而。那么drawRect方法為什么消耗的內(nèi)存比CAShapeLayer 大呢?
drawRect
如果想要了解drawRect
,那我們就需要擼一擼在iOS程序上圖形顯示的原理了。在iOS系統(tǒng)中所有顯示的視圖都是從基類UIView繼承而來的奸攻,同時(shí)UIView負(fù)責(zé)接收用戶交互。但是實(shí)際上你所看到的視圖內(nèi)容庇忌,包括圖形等舞箍,都是由UIView
的一個(gè)實(shí)例圖層屬性來繪制和渲染的,那就是CALayer
皆疹。
CALayer
類的概念與UIView
非常類似,它也具有樹形的層級(jí)關(guān)系占拍,并且可以包含圖片文本略就、背景色等捎迫。它與UIView
最大的不同在于它不能響應(yīng)用戶交互,可以說它根本就不知道響應(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
圖層才是真正用來在屏幕上顯示的敲茄,UIView
僅僅是對(duì)它的一層封裝位谋,實(shí)現(xiàn)了CALayer
的delegate
,提供了處理事件交互的具體功能堰燎,還有動(dòng)畫底層方法的高級(jí)API掏父。可以說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)?code>CALayer內(nèi)部有一個(gè)contents
屬性罐栈。contents
默認(rèn)可以傳一個(gè)id類型的對(duì)象黍衙,但是只有你傳CGImage
的時(shí)候,它才能夠正常顯示在屏幕上荠诬。所以最終我們的圖形渲染落點(diǎn)落在contents
身上琅翻。如圖:
contents
也被稱為寄宿圖,除了給它賦值CGImage
之外柑贞,我們也可以直接對(duì)它進(jìn)行繪制方椎,繪制的方法正是這次問題的關(guān)鍵,通過繼承UIView
并實(shí)現(xiàn) -drawRect:
方法即可自定義繪制钧嘶。但是呢drawRect:
方法沒有默認(rèn)的實(shí)現(xiàn)棠众,因?yàn)閷?duì)UIView
來說,寄宿圖并不是必須的,UIView不關(guān)心繪制的內(nèi)容闸拿。如果UIView檢測(cè)到-drawRect:
方法被調(diào)用了空盼,它就會(huì)為視圖分配一個(gè)寄宿圖,這個(gè)寄宿圖的像素尺寸等于視圖大小乘以contentsScale
(這個(gè)屬性與屏幕分辨率有關(guān)新荤,我們的雷達(dá)圖在不同模擬器下呈現(xiàn)的內(nèi)存用量不同也是因?yàn)樗?的值揽趾。
那么回到我們的雷達(dá)圖上,當(dāng)雷達(dá)圖從屏幕上出現(xiàn)的時(shí)候苛骨,因?yàn)橹貙懥?code>-drawRect:方法篱瞎,-drawRect :
就會(huì)自動(dòng)調(diào)用。生成一張寄宿圖后痒芝,方法里面的代碼利用Core Graphics
去繪制n條線條俐筋,然后內(nèi)容就會(huì)緩存起來,等待下次你調(diào)用-setNeedsDisplay
時(shí)就會(huì)在進(jìn)行更新吼野。
雷達(dá)視圖的-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é)議中的方法來拿瞳步。
當(dāng)雷達(dá)視圖刷新數(shù)據(jù)重繪時(shí)闷哆,因?yàn)樗闹С謭D層CALayer
的代理就是雷達(dá)視圖本身,所以支持圖層會(huì)請(qǐng)求雷達(dá)視圖給它一個(gè)寄宿圖來顯示单起,它此刻會(huì)調(diào)用:
- (void)displayLayer:(CALayer *)layer;
如果雷達(dá)視圖實(shí)現(xiàn)了這個(gè)方法抱怔,就可以拿到layer
來直接設(shè)置contents
寄宿圖,如果這個(gè)方法沒有實(shí)現(xiàn)嘀倒,支持圖層CALayer
會(huì)嘗試調(diào)用:
- (void)displayLayer:(CALayer *)layer;
這個(gè)方法調(diào)用之前屈留,CALayer
創(chuàng)建了一個(gè)合適尺寸的空寄宿圖(尺寸由bounds
和contentsScale
決定)和一個(gè)Core Graphics
的繪制上下文環(huán)境,為繪制寄宿圖做準(zhǔn)備测蘑,它作為ctx
參數(shù)傳入灌危。在這一步生成的空寄宿圖內(nèi)存是相當(dāng)巨大的,它就是本次內(nèi)存問題的關(guān)鍵碳胳,一旦你實(shí)現(xiàn)了CALayerDelegate
協(xié)議中的-drawLayer:inContext:
方法或者UIView
中的-drawRect:
方法(其實(shí)就是前者的包裝方法)勇蝙,圖層就創(chuàng)建了一個(gè)繪制上下文,這個(gè)上下文需要的內(nèi)存可從這個(gè)公式得出:圖層寬* 圖層高 * 4字節(jié)挨约,寬高的單位均為像素味混。 而既然我們的雷達(dá)圖是封裝的庫 就應(yīng)該適應(yīng)各個(gè)尺寸下內(nèi)存的要求,比如我們當(dāng)前的雷達(dá)圖尺寸下需要開辟的內(nèi)存空間大小為:
radarV = [[JYRadarChart alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, 400)];
那么我們?cè)?P機(jī)器 上下文的內(nèi)存量則是: 1242 * 1200 * 4字節(jié) 其實(shí)算下來大概有5兆多诫惭。在其內(nèi)部又有通過drawRect
方法去實(shí)現(xiàn)繪制文字 title
則加起來有 8M多
CAShapeLayer
CAShapeLayer
是一個(gè)通過矢量圖形而不是bitmap
來繪制的圖層子類翁锡。用CGPath
來定義想要繪制的圖形,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è)寄宿圖形梨熙,所以無論有多大开镣,都不會(huì)占用太多的內(nèi)存。 - 不會(huì)被圖層邊界剪裁掉咽扇。一個(gè)
CAShapeLayer
可以在邊界之外繪制邪财。你的圖層路徑不會(huì)像在使用Core Graphics
的普通CALayer
一樣被剪裁掉 - 不會(huì)出現(xiàn)像素化。當(dāng)你給
CAShapeLayer
做3D變換時(shí)质欲,它不像一個(gè)有寄宿圖的普通圖層一樣變得像素化
通過兩個(gè)demo的對(duì)比下 在當(dāng)前尺寸下確實(shí)內(nèi)存上會(huì)相差8M左右 树埠,如果其他設(shè)計(jì)要求雷達(dá)圖尺寸大一點(diǎn)的話 那么就會(huì)產(chǎn)生更大的內(nèi)存消耗。
在此總結(jié)一下繪制性能優(yōu)化原則:
- 1.繪制圖形性能的優(yōu)化最好的辦法就是不去繪制嘶伟。
- 2.利用專有圖層代替繪圖需求怎憋。
- 3.不得不用到繪圖盡量縮小視圖面積,并且盡量降低重繪頻率九昧。
- 4.異步繪制绊袋,推測(cè)內(nèi)容,提前在其他線程繪制圖片铸鹰,在主線程中直接設(shè)置圖片癌别。
當(dāng)然了,這兩種雷達(dá)圖的實(shí)現(xiàn)方式各有優(yōu)缺點(diǎn)蹋笼,也很感謝雷達(dá)圖作者開源展姐。
本人修改的兩種實(shí)現(xiàn)方式的雷達(dá)圖地址Demo,只用于粗略的對(duì)比剖毯。