iOS程序猿iOS Developer

如何解决UIImage加载大图,内存过大导致Carsh?

2021-06-30  本文已影响0人  DingGa

在本文中,与您分享我们面临的问题,高内存使用率的根本原因是什么,以及我们如何使用 Apple 工程师推荐的简单修复来减少应用程序的内存占用。

问题

如前所述,当我们开始在屏幕上加载高清图像时,我们的应用程序的内存使用量会激增。为了展示我们面临的问题,我创建了一个示例应用程序,当点击按钮时,它会将高清图像加载到图像视图中。


初始内存.png

    @IBAction func loadImage(_ sender: Any) {
        image.image = UIImage(named: "image.JPG")
    }

截屏2021-06-30 上午11.55.17.png 加载图片后内存.png

请注意,我使用的是尺寸为 6240 × 4160px 的图像,其文件大小为 9MB。

如您所见,加载图像后内存使用量从 9MB 飙升至 155MB。

这完全没有道理!我们正在加载一个 9MB 的图像文件,内存占用怎么会这么高?所有这些内存使用来自哪里?😫

内存使用 ≠ 文件大小

我们用来解决这个问题的一种方法是缩小图像并根据图像视图的帧大小将其绘制在屏幕上,不幸的是,这似乎并没有解决我们的问题。

在没有任何关于如何解决问题的线索的情况下,我转向了网络。在谷歌搜索了几次之后,我得到了一个惊人的 WWDC 视频,它引入了一个我们都不知道的非常重要的概念:

内存使用与图像的尺寸有关,与文件大小无关。

为了在屏幕上显示图像,iOS 首先需要对图像进行解码和解压缩。通常,解码图像的 1 个像素将占用 4 个字节的内存

以我们尺寸为 6240 × 4160px 的示例图片为例:

(6240 × 4160) * 4字节 = 103833600 bytes  
差不多 103.8336 MB

这确实与我们在内存报告中看到的想对接近一点了(由于图片场景格式问题,会有所偏差)

降采样来拯救

我们已经弄清楚内存使用的来源,但是我们如何将内存占用减少到可接受的水平呢?

正如我之前提到的,我们尝试缩小并重新绘制图像,但这似乎没有帮助。这是因为UIImage调整大小和调整大小很昂贵。在调整大小的过程中,iOS 仍然会解码和解压缩原始图像,从而导致不必要的内存峰值。

使用 ImageIO 进行下采样

幸运的是,WWDC18 为我们面临的问题提供了很好的解决方案。我们可以使用该ImageIO框架在图像显示在屏幕上之前调整其大小。这种ImageIO方法的伟大之处在于,我们现在只需支付调整大小图像的成本就可以调整图像大小。我们称这种方法为“下采样”。

以下代码片段演示了如何对图像执行下采样:

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)
}

让我们先来看看函数的参数:

选项标志

接下来,我想提请你注意选项标志3的功能被使用- kCGImageSourceShouldCachekCGImageSourceShouldCacheImmediatelykCGImageSourceCreateThumbnailWithTransform

首先,让我们谈谈kCGImageSourceShouldCache。当此标志设置为 false 时,我们让核心图形框架知道我们只需要创建对图像源的引用,并且不想CGImageSource在创建对象时立即解码图像。

提示:在您无法访问图像源路径的情况下,您可以CGImageSource使用CGImageSourceCreateWithData()初始化程序创建一个对象。

列表中的下一个是kCGImageSourceShouldCacheImmediately。这个标志表示核心图形框架应该在我们开始下采样过程的那一刻解码图像。

因此,通过同时使用kCGImageSourceShouldCachekCGImageSourceShouldCacheImmediately选项标志,我们可以完全控制何时要占用 CPU 进行图像解码。

最后是kCGImageSourceCreateThumbnailWithTransform选项标志。将此标志设置为 true 非常重要,因为它让核心图形框架知道您希望下采样图像与原始图像具有相同的方向。

让我们看看结果

现在让我们尝试再次在屏幕上加载我们的示例图像,但这次启用了下采样。

let filePath = Bundle.main.url(forResource: "image", withExtension: "JPG")!
let downsampledLadyImage = downsample(imageAt: filePath, to: imageView.bounds.size)
imageView.image = downsampledLadyImage
最终内存.png

你已经看到了下采样的结果,它非常惊人。但这并不意味着您应该开始将高清图像捆绑到您的应用程序中并在需要时对它们进行下采样。

请记住,下采样是一个占用 CPU 能力的过程。因此,最好使用适当压缩后的图像源,而不是对 HD 图像进行下采样。
换句话说,只有当您需要显示尺寸远高于屏幕上所需尺寸的图像时,才应考虑使用下采样。

本文demo Github地址: https://github.com/stevendinggang/UIImageMemory

本文参考: https://developer.apple.com/wwdc18/219

上一篇下一篇

猜你喜欢

热点阅读