上一篇我們說過,在使用
UIKit框架
的過程中,性能優(yōu)化是永恒的話題
。
也為大家講了部分
UIKit性能調(diào)優(yōu)
的知識(shí),這篇文章將繼續(xù)為大家用實(shí)戰(zhàn)詳細(xì)講解。
顏色格式
像素在內(nèi)存中的布局和它在磁盤中的存儲(chǔ)方式并不相同??紤]一種簡單的情況:每個(gè)像素有
R
、
G
、
B
和
alpha
四個(gè)值,每個(gè)值占用
1
字節(jié),因此每個(gè)像素占用
4
字節(jié)的內(nèi)存空間。一張
1920*1080
的照片
(iPhone6 Plus
的分辨率
)
一共有
2,073,600
個(gè)像素,因此占用了超過
8Mb
的內(nèi)存。但是一張同樣分辨率的
PNG
格式或
JPEG
格式的圖片一般情況下不會(huì)有這么大。這是因?yàn)?/span>
JPEG
將像素?cái)?shù)據(jù)進(jìn)行了一種非常復(fù)雜且可逆的轉(zhuǎn)化。
當(dāng)我們打開
JPEG
格式的圖片時(shí),
CPU
會(huì)進(jìn)行一系列運(yùn)算,將
JPEG
圖片解壓成像素?cái)?shù)據(jù)。顯然這個(gè)工作會(huì)消耗不少時(shí)間,所以不應(yīng)該在滑動(dòng)時(shí)進(jìn)行,我們應(yīng)該預(yù)先處理好圖片。借用
WWDC
上的一頁
PPT
來說明:
顯示流程
Commit Transaction
和
Decode
在同一幀內(nèi)進(jìn)行,如果這兩個(gè)操作的耗時(shí)超過
16.67s
,
Draw Calls
就會(huì)延遲到下一幀,從而導(dǎo)致
fps
值的降低。下面是
Commit Transaction
的詳細(xì)流程:
解碼與轉(zhuǎn)換
在第三步的
Prepare
中,
CPU
主要處理兩件事:
把圖片從
PNG
或
JPEG
等格式中解壓出來,得到像素?cái)?shù)據(jù)。
如果
GPU
不支持這種顏色各式,
CPU
需要進(jìn)行格式轉(zhuǎn)換。
比如應(yīng)用中有一些從網(wǎng)絡(luò)下載的圖片,而
GPU
恰好不支持這個(gè)格式,這就需要
CPU
預(yù)先進(jìn)行格式轉(zhuǎn)化。第三個(gè)選項(xiàng)
“Color Copied Images”
就用來檢測這種實(shí)時(shí)的格式轉(zhuǎn)化,如果有則會(huì)將圖片標(biāo)記為藍(lán)色。
遺憾的是由于我對圖片格式不太了解,也不會(huì)使用相關(guān)工具,并沒有能模擬出觸發(fā)這個(gè)選項(xiàng)的場景。我們要記住的是,如果調(diào)試時(shí)發(fā)現(xiàn)有圖片被標(biāo)記為藍(lán)色,說明圖片格式出現(xiàn)了一些問題。
圖片大小
第四個(gè)選項(xiàng)的使用場景不多,我們直接看一下第五個(gè)選項(xiàng)
“Color Misaligned Images”
。它表示如果圖片需要縮放則標(biāo)記為**,如果沒有像素對齊則標(biāo)記為紫色。勾選上這個(gè)選項(xiàng)并進(jìn)行調(diào)試,可以看到如下場景:
圖片縮放
在
demo
中,每個(gè)
UIImageView
的大小都是
180x180
,而只有第二張圖片的像素大小是
360x360
。因此除了第二張圖片,其他的圖片都需要被縮放。圖片的縮放需要占用時(shí)間,因此我們要盡可能保證無論是本地圖片還是從網(wǎng)絡(luò)或取得圖片的大小,都與其
frame
保持一致。
第三個(gè)優(yōu)化是調(diào)整所有圖片的像素大小以避免不必要的縮放。
離屏渲染
離屏渲染表示渲染發(fā)生在屏幕之外,你可能認(rèn)為這是一句廢話。為了真正解釋清楚什么是離屏渲染,我們先來看一下正常的渲染通道
(Render-Pass)
:
正常渲染通道
首先,
OpenGL
提交一個(gè)命令到
Command Buffer
,隨后
GPU
開始渲染,渲染結(jié)果放到
Render Buffer
中,這是正常的渲染流程。但是有一些復(fù)雜的效果無法直接渲染出結(jié)果,它需要分步渲染最后再組合起來,比如添加一個(gè)蒙版
(mask)
:
離屏渲染
在前兩個(gè)渲染通道中,
GPU
分別得到了紋理
(texture
,也就是那個(gè)相機(jī)圖標(biāo)
)
和
layer(
藍(lán)色的蒙版
)
的渲染結(jié)果。但這兩個(gè)渲染結(jié)果沒有直接放入
Render Buffer
中,也就表示這是離屏渲染。直到第三個(gè)渲染通道,才把兩者組合起來放入
Render Buffer
中。離屏渲染意味著把渲染結(jié)果臨時(shí)保存,等用到時(shí)再取出,因此相對于普通渲染更占用資源。
第六個(gè)選項(xiàng)
“Color Offscreen-Rendered Yellow”
會(huì)把需要離屏渲染的地方標(biāo)記為**,大部分情況下我們需要盡可能避免**的出現(xiàn)。離屏渲染可能會(huì)自動(dòng)觸發(fā),也可以手動(dòng)觸發(fā)。以下情況可能會(huì)導(dǎo)致觸發(fā)離屏渲染:
重寫
drawRect
方法
有
mask
或者是陰影
(layer.masksToBounds, layer.shadow*)
,模糊效果也是一種
mask
layer.shouldRasterize = true
前兩者會(huì)自動(dòng)觸發(fā)離屏渲染,第三種方法是手動(dòng)開啟離屏渲染。
開始調(diào)試并勾選
“Color Offscreen-Rendered Yellow”
,會(huì)看到這樣的場景:
離屏渲染
如果沒有進(jìn)行第二步優(yōu)化,你會(huì)發(fā)現(xiàn)
label
也是**??梢钥吹?/span>
tabbar
和
statusBar
也是**,這是因?yàn)樗鼈兪褂昧四:Ч?。圖片也是**,這說明它也進(jìn)行了離屏渲染,觀察源碼后發(fā)現(xiàn)主要原因是它使用了陰影,接下來我們進(jìn)行第四個(gè)優(yōu)化,在設(shè)置陰影效果的四行代碼下面添加一行:
|
imgView.layer.shadowPath = UIBezierPath(rect: imgView.bounds).CGPath
|
這行代碼制定了陰影路徑,如果沒有手動(dòng)指定,
Core Animation
會(huì)去自動(dòng)計(jì)算,這就會(huì)觸發(fā)離屏渲染。如果人為指定了陰影路徑,就可以免去計(jì)算,從而避免產(chǎn)生離屏渲染。
設(shè)置
cornerRadius
本身并不會(huì)導(dǎo)致離屏渲染,但很多時(shí)候它還需要配合
layer.masksToBounds = true
使用。根據(jù)之前的總結(jié),設(shè)置
masksToBounds
會(huì)導(dǎo)致離屏渲染。解決方案是盡可能在滑動(dòng)時(shí)避免設(shè)置圓角,如果必須設(shè)置圓角,可以使用光柵化技術(shù)將圓角緩存起來:
|
//
設(shè)置圓角
label.layer.masksToBounds = true
label.layer.cornerRadius = 8
label.layer.shouldRasterize = true
label.layer.rasterizationScale = layer.contentsScale
|
快速路徑
還記得之前將離屏渲染和渲染路徑時(shí)的示意圖么,離屏渲染的最后一步是把此前的多個(gè)路徑組合起來。如果這個(gè)組合過程能由
CPU
完成,就會(huì)大量減少
GPU
的工作。這種技術(shù)在繪制地圖中可能用到。
第七個(gè)選項(xiàng)
“Color Compositing Fast-Path Blue”
用于標(biāo)記由硬件繪制的路徑,藍(lán)色越多越好。
變化區(qū)域
刷新視圖時(shí),我們應(yīng)該把需要重繪的區(qū)域盡可能縮小。對于未發(fā)生變化的內(nèi)容則不應(yīng)該重繪,第八個(gè)選項(xiàng)
“Flash updated Regions”
用于標(biāo)記發(fā)生重繪的區(qū)域。一個(gè)典型的例子是系統(tǒng)的時(shí)鐘應(yīng)用,絕大多數(shù)時(shí)候只有顯示秒針的區(qū)域需要重繪:
重繪區(qū)域
原文來自:
bestswifter