【问题标题】:Why in Swift5, the UIImage.jpegData() call will retain that much memory?为什么在 Swift5 中, UIImage.jpegData() 调用会保留那么多内存?
【发布时间】:2020-09-06 14:29:15
【问题描述】:

我有一个应用程序运行以下代码 sn-ps --

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

并且这个内存峰值不会消失,直到整个视图被释放。我确实尝试了一些 autoreleasepool() 或其他放入 DispatchQueue() 的操作,如swift UIGraphicsGetImageFromCurrentImageContext can't release memory 中所建议的那样,但显然它们不起作用。

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

我不愿将其称为内存泄漏,因为我相信这个对象(我实际上不知道为什么 UIImage.jpegData() 调用也会消耗那么多内存,看起来像 Swift 内存管理算法的错误)最终被回收时视图已卸载。但是有什么办法可以避免jpegData转换造成的巨大内存消耗或者释放内存呢?

(在我的 iPhone 上以 4800*6000Pixels 加载 24MB 图像时,使用此 UIImage.jpegData() 操作,内存将增加约 100MB)。

【问题讨论】:

  • 您能否澄清一下您的代码 sn-p 与您的“视图”有何关系。上面的代码是 UIView 的子类吗?
  • 不显示“sn-ps”。在上下文中向我们展示实际的代码。我们不知道您的代码是如何运行的,也不知道图像来自何处、数据去向。

标签: swift jpeg swift5


【解决方案1】:

4800*6000 = 28.8M,@ 每像素 4 字节 (RGBA) = 1.15 亿字节。所以~100MB 对于未压缩的数据听起来完全正确,这正是您在generateImage 中创建的。 CoreGraphics 试图在这些事情上变得懒惰,所以在你强制它渲染所有东西之前你可能看不到实际成本(当你要求 jpegData 时)。

您编写的代码实际上没有意义(您从未定义outputImage 或使用newImage),所以我们不得不猜测很多。您可能将newImageoutputImage 存储在某处而没有向我们展示。也许在一个属性中,它会匹配“当这个视图被释放时”。

但您似乎也在使用imageView 显示此图像。如果是这样,那将消耗大约 100MB,因为它必须解压缩才能在屏幕上绘制它。因此,您很可能只是查看图像视图的内存使用情况,这也与“何时释放此视图”相匹配。如果是这种情况,我预计内存使用量会飙升到两倍以上(因为你也渲染它,然后压缩它)。但是你可能看不到那个峰值,因为当你用完它时内存会被释放,只剩下 100MB 用于图像视图。

(这个“100MB 用于图像视图”在很大程度上取决于您设置图像视图的方式。如果它直接从文件中读取,它可能有内存映射压缩数据,并且应该只渲染到视图的实际大小。UIImageView 很聪明。但是显示大图像总是会消耗大量内存。最终你需要字节。)

有点不清楚这段代码试图做什么。我怀疑它可以更有效地完成(鉴于您似乎已经有一个图像视图,我不确定您为什么要渲染;您应该能够直接操作底层图像)。

但最终,如果您想要 RGBA 格式的 4800*6000 像素图像,则需要大约 100MB 的工作内存来操作。这就是大图像的成本。如果内存过多,则需要减少正在处理的像素数。

【讨论】:

  • 谢谢罗伯。但是,您所指的“未压缩数据”是用于处理 JPEG 的临时变量吗?为什么这个内存在 UIImage.jpegData() 之后保留?
  • 为了显示图像(即在图像视图中),您需要一个未压缩的副本。但我的另一点是你没有给我们很多代码,你可能将generateImage 的结果存储在一个属性中,在这种情况下它不会被释放。
【解决方案2】:

有趣的是,以下代码解决了这个问题,显然问题来自 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,幸运的是,通过这个更新,在将数据放入某些类变量的调用之后,不会保留 Rob 提到的用于将 UIImage 转换为 JPEG 数据的内存/缓存。但是我仍然不太明白其中的原因。也许人们可以发表更多评论?

【讨论】:

  • 这就是答案吗?还是您要对问题进行扩展? generateMosaicImage 是什么?
  • 这将在返回之前释放未压缩的数据,而不是返回未压缩的数据。根据您对返回值的处理,这可能非常有意义。很难说,因为您没有显示大部分重要代码,并且您发布的代码与您描述的代码不太匹配,所以我们猜测很多。比如你改generateImage()的时候,显然你也改了调用代码,因为签名不一样。但你没有向我们展示这种变化。
猜你喜欢
  • 2012-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-19
  • 1970-01-01
  • 1970-01-01
  • 2016-04-16
  • 2014-02-17
相关资源
最近更新 更多