为什么在Swift5中,UIImage.jpegData调用将保留那么多内存?

问题描述

我有一个运行以下代码段的应用程序-

...
let newImage = generateImage()
let imageData = newImage.jpegData(compressionQuality: 1)

// operations with the imageData. The memory consumption of the
// imageData object looks reasonable,2-3 MB for a 4800*6000
// pixel image.
...

func generateImage() -> UIImage {
    let canvasSize = self.outputImage.size
    UIGraphicsBeginImageContext(canvasSize)
    self.imageView.zoomView?.layer.render(in: UIGraphicsGetCurrentContext()!)
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return newImage!
}

观察到,在以下代码的步骤中,我将看到内存峰值

let imageData = outputImage.jpegData(compressionQuality: 1)

并且,直到释放整个视图,此内存峰值才会消失。我确实按照此处swift UIGraphicsGetImageFromCurrentImageContext can't release memory的建议尝试了一些autoreleasepool()或其他放置在DispatchQueue()中的操作,但是显然它们不起作用。

为了减少混乱,generateImage()调用不会产生内存泄漏,而只会产生峰值,即,在generateImage()调用之后,内存会立即下降,但我只是将其放在那里查看是否相关(我尝试过一些带有jpegData()的UIImage,但是我无法100%重现此问题)。

我毫不犹豫地将其称为内存泄漏,因为我相信这个对象(我实际上不知道为什么UIImage.jpegData()调用也消耗了那么多内存,看起来像Swift内存管理算法的错误)最终在视图已卸载。但是有什么方法可以避免由于jpegData转换而导致的大量内存消耗或释放内存?

(当在具有4800 * 6000Pixels的iPhone上加载24MB图像时,通过UIImage.jpegData()操作,内存将增加约100MB)。

解决方法

4800 * 6000 = 28.8M,@每像素4个字节(RGBA)= 1.15亿字节。因此,对于未压缩的数据,大约100MB听起来完全正确,这正是您在generateImage中创建的。 CoreGraphics试图对这些事情保持懒惰,因此您可能看不到实际成本,除非您强迫它渲染所有内容(当您要求jpegData时)。

您编写的代码实际上没有任何意义(您永远不会定义outputImage或使用newImage),因此我们不得不猜测很多。您可能将newImageoutputImage存储在某个地方而没有向我们显示。也许在一个属性中,该属性将与“释放此视图时”匹配。

但是您似乎也正在使用imageView显示此图像。如果是这样,那将要吃掉约100MB,因为它必须解压缩才能在屏幕上绘制。因此,很有可能您只是在查看图像视图的内存使用情况,这也将与“释放此视图时”相匹配。如果是这种情况,我希望内存使用量会增加到两倍(因为您还要渲染它,然后对其进行压缩)。但是您可能看不到峰值,因为使用完该内存后,内存将被释放,仅留出100MB用于图像视图。

(此“用于图像视图的100MB”在很大程度上取决于您如何设置图像视图。如果直接从文件中读取文件,则可能是内存映射了压缩数据,因此仅应渲染到视图的实际大小。UIImageView非常智能。但是显示大图像始终会占用大量内存。最终,您需要字节。)

尚不清楚此代码将要执行的操作。我怀疑这样做可能会更有效率(鉴于您似乎已经拥有图像视图,我不确定为什么要渲染;应该可以直接操作基础图像)。

但是最终,如果要在RGBA中使用4800 * 6000像素的图像,则需要大约100MB的工作内存来进行操作。那就是大图像的成本。如果内存过多,则需要减少正在处理的像素数。

,

有趣的是,以下代码解决了该问题,显然该问题来自generateImage()方法,但我无法解释原因。

func generateImage() -> Data {
    // NOTE do not put any of these codes out in case of retain issues of
    // the JPEG data or the images.
    let canvasSize = self.outputImage.size
    UIGraphicsBeginImageContext(canvasSize)
    self.imageView.zoomView?.layer.render(in: UIGraphicsGetCurrentContext()!)
    let newImage = UIGraphicsGetImageFromCurrentImageContext()?.jpegData(compressionQuality: 0)
    UIGraphicsEndImageContext()
    return newImage!
}

我更新了此方法以直接返回数据,而不是返回UIImage,幸运的是,通过此更新,在将Data放入某些类变量的调用之后,没有保留Rob提到的将UIImage转换为JPEG数据的内存。 / caches。但是我仍然不太了解原因。人们可能会发表更多评论?

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...