问题描述
我有一个运行以下代码段的应用程序-
...
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
),因此我们不得不猜测很多。您可能将newImage
或outputImage
存储在某个地方而没有向我们显示。也许在一个属性中,该属性将与“释放此视图时”匹配。
但是您似乎也正在使用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。但是我仍然不太了解原因。人们可能会发表更多评论?