在本文中,與您分享我們面臨的問(wèn)題,高內(nèi)存使用率的根本原因是什么便斥,以及我們?nèi)绾问褂?Apple 工程師推薦的簡(jiǎn)單修復(fù)來(lái)減少應(yīng)用程序的內(nèi)存占用。
問(wèn)題
如前所述威始,當(dāng)我們開始在屏幕上加載高清圖像時(shí)枢纠,我們的應(yīng)用程序的內(nèi)存使用量會(huì)激增。為了展示我們面臨的問(wèn)題字逗,我創(chuàng)建了一個(gè)示例應(yīng)用程序京郑,當(dāng)點(diǎn)擊按鈕時(shí),它會(huì)將高清圖像加載到圖像視圖中葫掉。
@IBAction func loadImage(_ sender: Any) {
image.image = UIImage(named: "image.JPG")
}
請(qǐng)注意,我使用的是尺寸為 6240?×?4160px 的圖像跟狱,其文件大小為 9MB俭厚。
如您所見,加載圖像后內(nèi)存使用量從 9MB 飆升至 155MB驶臊。
這完全沒有道理挪挤!我們正在加載一個(gè) 9MB 的圖像文件,內(nèi)存占用怎么會(huì)這么高关翎?所有這些內(nèi)存使用來(lái)自哪里扛门???
內(nèi)存使用 ≠ 文件大小
我們用來(lái)解決這個(gè)問(wèn)題的一種方法是縮小圖像并根據(jù)圖像視圖的幀大小將其繪制在屏幕上,不幸的是纵寝,這似乎并沒有解決我們的問(wèn)題论寨。
在沒有任何關(guān)于如何解決問(wèn)題的線索的情況下,我轉(zhuǎn)向了網(wǎng)絡(luò)。在谷歌搜索了幾次之后葬凳,我得到了一個(gè)驚人的 WWDC 視頻绰垂,它引入了一個(gè)我們都不知道的非常重要的概念:
內(nèi)存使用與圖像的尺寸有關(guān),與文件大小無(wú)關(guān)火焰。
為了在屏幕上顯示圖像劲装,iOS 首先需要對(duì)圖像進(jìn)行解碼和解壓縮。通常昌简,解碼圖像的 1 個(gè)像素將占用 4 個(gè)字節(jié)的內(nèi)存
- 1 個(gè)字節(jié)用于紅色占业,
- 1 個(gè)字節(jié)用于綠色,
- 1 個(gè)字節(jié)用于藍(lán)色纯赎,
- 1 個(gè)字節(jié)用于 alpha 分量谦疾。
以我們尺寸為 6240?×?4160px 的示例圖片為例:
(6240?×?4160) * 4字節(jié) = 103833600 bytes
差不多 103.8336 MB
這確實(shí)與我們?cè)趦?nèi)存報(bào)告中看到的想對(duì)接近一點(diǎn)了(由于圖片場(chǎng)景格式問(wèn)題,會(huì)有所偏差)
降采樣來(lái)拯救
我們已經(jīng)弄清楚內(nèi)存使用的來(lái)源,但是我們?nèi)绾螌?nèi)存占用減少到可接受的水平呢址否?
正如我之前提到的餐蔬,我們嘗試縮小并重新繪制圖像,但這似乎沒有幫助佑附。這是因?yàn)?code>UIImage調(diào)整大小和調(diào)整大小很昂貴樊诺。在調(diào)整大小的過(guò)程中,iOS 仍然會(huì)解碼和解壓縮原始圖像音同,從而導(dǎo)致不必要的內(nèi)存峰值词爬。
使用 ImageIO 進(jìn)行下采樣
幸運(yùn)的是,WWDC18 為我們面臨的問(wèn)題提供了很好的解決方案权均。我們可以使用該ImageIO
框架在圖像顯示在屏幕上之前調(diào)整其大小顿膨。這種ImageIO
方法的偉大之處在于,我們現(xiàn)在只需支付調(diào)整大小圖像的成本就可以調(diào)整圖像大小叽赊。我們稱這種方法為“下采樣”恋沃。
以下代碼片段演示了如何對(duì)圖像執(zhí)行下采樣:
func downsample(imageAt imageURL: URL,
to pointSize: CGSize,
scale: CGFloat = UIScreen.main.scale) -> UIImage? {
// Create an CGImageSource that represent an image
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions) else {
return nil
}
// Calculate the desired dimension
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
// Perform downsampling
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
] as CFDictionary
guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
return nil
}
// Return the downsampled image as UIImage
return UIImage(cgImage: downsampledImage)
}
讓我們先來(lái)看看函數(shù)的參數(shù):
- imageURL:圖片網(wǎng)址。它可以是 Web URL 或本地圖像路徑必指。
- pointSize:下采樣圖像的所需大小囊咏。通常,這將是UIImageView的幀大小塔橡。
- scale:下采樣比例因子梅割。通常,這將是與屏幕相關(guān)的比例因子(我們通常將其稱為@2x或@3x)葛家。這就是為什么您可以看到其默認(rèn)值已設(shè)置為UIScreen.main.scale.
選項(xiàng)標(biāo)志
接下來(lái)户辞,我想提請(qǐng)你注意選項(xiàng)標(biāo)志3的功能被使用- kCGImageSourceShouldCache
,kCGImageSourceShouldCacheImmediately
和kCGImageSourceCreateThumbnailWithTransform
癞谒。
首先底燎,讓我們談?wù)?code>kCGImageSourceShouldCache刃榨。當(dāng)此標(biāo)志設(shè)置為 false
時(shí),我們讓核心圖形框架知道我們只需要?jiǎng)?chuàng)建對(duì)圖像源的引用书蚪,并且不想CGImageSource
在創(chuàng)建對(duì)象時(shí)立即解碼圖像喇澡。
提示:在您無(wú)法訪問(wèn)圖像源路徑的情況下,您可以
CGImageSource
使用CGImageSourceCreateWithData()
初始化程序創(chuàng)建一個(gè)對(duì)象殊校。
列表中的下一個(gè)是kCGImageSourceShouldCacheImmediately
晴玖。這個(gè)標(biāo)志表示核心圖形框架應(yīng)該在我們開始下采樣過(guò)程的那一刻解碼圖像。
因此为流,通過(guò)同時(shí)使用kCGImageSourceShouldCache
和kCGImageSourceShouldCacheImmediately
選項(xiàng)標(biāo)志呕屎,我們可以完全控制何時(shí)要占用 CPU 進(jìn)行圖像解碼。
最后是kCGImageSourceCreateThumbnailWithTransform
選項(xiàng)標(biāo)志敬察。將此標(biāo)志設(shè)置為 true
非常重要秀睛,因?yàn)樗尯诵膱D形框架知道您希望下采樣圖像與原始圖像具有相同的方向。
讓我們看看結(jié)果
現(xiàn)在讓我們嘗試再次在屏幕上加載我們的示例圖像莲祸,但這次啟用了下采樣蹂安。
let filePath = Bundle.main.url(forResource: "image", withExtension: "JPG")!
let downsampledLadyImage = downsample(imageAt: filePath, to: imageView.bounds.size)
imageView.image = downsampledLadyImage
你已經(jīng)看到了下采樣的結(jié)果,它非常驚人锐帜。但這并不意味著您應(yīng)該開始將高清圖像捆綁到您的應(yīng)用程序中并在需要時(shí)對(duì)它們進(jìn)行下采樣田盈。
請(qǐng)記住,下采樣是一個(gè)占用 CPU 能力的過(guò)程缴阎。因此允瞧,最好使用適當(dāng)壓縮后的圖像源,而不是對(duì) HD 圖像進(jìn)行下采樣蛮拔。
換句話說(shuō)述暂,只有當(dāng)您需要顯示尺寸遠(yuǎn)高于屏幕上所需尺寸的圖像時(shí),才應(yīng)考慮使用下采樣建炫。
本文demo Github地址: https://github.com/stevendinggang/UIImageMemory