一、圖像如何顯示
內(nèi)存使用與圖像的尺寸有關(guān),而不是與文件大小有關(guān)。
舉個例子,我有一張非常漂亮的圖片,我想用它作為iPad應(yīng)用程序的墻紙。
它的大小是2048??1536,磁盤上的文件是590KB。
但是它到底用了多少內(nèi)存呢?10MB。
10MB,太大了!其原因是,將寬像素數(shù)*高像素數(shù):2048??1536,再??每像素4個字節(jié),得到大約10MB。
那它為什么這么大呢?好吧,我們得談?wù)剤D像在iOS上是如何工作的:
1. 有加載-解碼-渲染階段:
加載階段將這個590KB的JPEG文件壓縮后加載到內(nèi)存中。
解碼器將該JPEG文件轉(zhuǎn)換為GPU可以讀取的格式。
現(xiàn)在,這需要解壓縮,這使得它有10MB。

一旦被解碼,就可以隨意渲染。
2. 圖片格式:
2.1 SRGB格式
我們用SRGB格式得到了每像素4個字節(jié)。
這通常是圖形中最常見的圖像格式。
它是每像素8位,所以你有1個字節(jié)代表紅色,1個字節(jié)代表綠色,1個字節(jié)代表藍色,還有一個alpha分量。

不過,我們可以做得更大一些。
2.2 寬格式(wide format)
iOS硬件可以呈現(xiàn)寬格式(wide format)。
現(xiàn)在,寬格式為了得到有表現(xiàn)力的顏色,每像素需要2個字節(jié),所以我們把圖像的大小增加了一倍。

iPhone7、8、X和iPhone上的攝像頭,一些iPad專業(yè)人士非常適合捕捉這種高保真的內(nèi)容。
你也可以使用它的超級準(zhǔn)確的顏色,如體育標(biāo)志等。
但是這些只有在寬格式顯示上才真正有用,所以我們不想在不需要的時候使用它。
2.3 luminance and alpha 8(AL8)
另一方面,我們實際上可以縮小規(guī)模。
現(xiàn)在,有一個亮度和alpha 8格式。

此格式僅存儲灰度和alpha值。
這通常用于著色器,就像金屬應(yīng)用程序之類。
在我們的使用中不太常見。
2.4 alpha 8(AL8)
實際上我們可以變得更小。
我們可以繼續(xù)使用alpha8格式。

現(xiàn)在,alpha8只有一個通道,每像素一個字節(jié)。
非常小。
它比SRGB小75%。
現(xiàn)在,這很適合蒙版或單色的文本,因為我們使用75%的內(nèi)存。
3. 選擇正確格式
如果我們看一下細(xì)分,我們可以從alpha8的每像素1字節(jié)一直到寬格式的每像素8字節(jié)。
范圍很大。
所以我們真正需要做的是知道如何選擇正確的格式。
那么我們?nèi)绾芜x擇正確的格式呢?簡單的回答是
不要選擇格式。
讓格式選擇你。
如果您從使用UIGraphics BeginImageContext with options API遷移到iOS,而改為使用UIGraphics ImageRenderer格式,則可以節(jié)省大量內(nèi)存,因為UIGraphics BeginImageContext with options始終是每像素4字節(jié)的格式。
它總是SRGB。
所以如果你想要的話,你不能得到寬格式,如果你需要的話,你也不能得到每像素1字節(jié)的A8格式。
相反,如果您使用iOS 10中的UIGraphics ImageRenderer API(從iOS 12開始),它將自動為您選擇最佳的圖形格式。
假設(shè)我畫了一個圓。
現(xiàn)在,使用舊的API和突出顯示的代碼是我的繪圖代碼,我得到的是每像素4字節(jié)的格式,只是為了畫一個黑色的圓。
/**
使用舊的API
*/
let bounds = CGRect(x: 0, y: 0, width:300, height: 100)
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)
// Drawing Code
UIColor.black.setFill()
let path = UIBezierPath(roundedRect: bounds,
path.addClip()
UIRectFill(bounds)
byRoundingCorners: UIRectCorner.allCorners,
cornerRadii: CGSize(width: 20, height: 20))
path.addClip()
UIRectFill(bounds)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
/**
使用新的API
*/
// Circle via UIGraphicsImageRenderer
let bounds = CGRect(x: 0, y: 0, width:300, height: 100)
let renderer = UIGraphicsImageRenderer(size: bounds.size)
let image = renderer.image { context in
// Drawing Code
UIColor.black.setFill()
let path = UIBezierPath(roundedRect: bounds,
path.addClip()
UIRectFill(bounds)
byRoundingCorners: UIRectCorner.allCorners,
cornerRadii: CGSize(width: 20, height: 20))
}
如果我改為使用新的API,我使用的是完全相同的繪圖代碼。
僅僅使用新的API,我現(xiàn)在就得到了每像素1字節(jié)的圖像。
這意味著它的內(nèi)存使用減少了75%。
這是一個巨大的節(jié)約和同樣的保真度。
另外一個好處,如果我想再次使用這個蒙版,我可以改變UIimageView的tintColor,這用一個·就可以做到,這意味著我不必分配更多的內(nèi)存。
// Make circle render blue, but stay at 1 byte-per-pixel image
let imageView = UIImageView(image: image)
imageView.tintColor = .blue
因此,我可以不僅把它作為一個黑色的圓圈,還可以作為一個藍色的圓圈,紅色的圓圈,綠色的圓圈,而沒有額外的內(nèi)存成本。
參考資料:
1.iOS圖片內(nèi)存優(yōu)化
2.WWDC2018 圖像最佳實踐
3.iOS核心動畫高級技術(shù)
