使用 CIImage 支持的 UIImage 设置 UIImageView 时很少发生崩溃
Rare crashes when setting UIImageView with a UIImage backed with CIImage
首先,我想强调的是,根据 Firebase Crashlytics,此错误仅涉及 1% 的用户群。
我有一个包含许多 heic 图像的 xcasset 目录。
我需要显示一些这样的图像(原始版本)和一些模糊的图像。
这是加载和显示正常图像或模糊图像的代码。
// Original image
self.imageView.image = UIImage(named: "officeBackground")!
// Blurred image
self.imageView.image = AssetManager.shared.blurred(named: "officeBackground")
我使用管理器来缓存模糊图像,这样我就不必在每次显示它们时都重新生成它们。
final class AssetManager {
static let shared = AssetManager()
private var blurredBackground = [String: UIImage]()
func blurred(named: String) -> UIImage {
if let cachedImage = self.blurredBackground[from] {
return cachedImage
}
let blurred = UIImage(named: named)!.blurred()!
self.blurredBackground[from] = blurred
return blurred
}
}
最后是模糊代码
extension UIImage {
func blurred() -> UIImage? {
let ciimage: CIImage? = self.ciImage ?? CIImage(image: self)
guard let input = ciimage else { return nil }
let blurredImage = input.clampedToExtent()
.applyingFilter("CIGaussianBlur", parameters: [kCIInputRadiusKey: 13])
.cropped(to: input.extent)
return UIImage(ciImage: blurredImage, scale: self.scale, orientation: .up)
}
}
这是我遇到的两种崩溃类型
- 带有 CFAutorelease 的 CoreFoundation。 Crashlytics 有关于它的附加信息:
crash_info_entry_0:
*** CFAutorelease() called with NULL ***
- CoreImage 与 recursive_render。 Crashlytics 还有关于它的附加信息:
crash_info_entry_0:
Cache Stats: count=14 size=100MB non-volatile=0B peakCount=28 peakSize=199MB peakNVSize=50MB
我发现所有用户之间唯一的共同点是在崩溃时他们有 30 - 150 Mo 的 RAM(根据 Firebase,这个信息是否可靠?)。
说到这里,我真的是一头雾水。这似乎是 CoreImage / CoreFoundation 的一个错误,它如何处理内存中的 CIImage。
奇怪的是,因为我使用 AssetManager 来缓存模糊图像,我知道在崩溃期间用户已经在 RAM 中有可用的缓存版本,但是当设置 UIImageView 时缓存图像,它因内存不足而崩溃(?!)。为什么系统甚至试图分配内存来执行此操作?
根据我的经验,直接使用从 CIImage
创建的 UIImage
是非常不可靠和错误的。主要原因是CIImage
不是真正的位图图像,而是包含创建图像说明的收据 . UIImage
的消费者知道它由 CIImage
支持并正确呈现它。 UIImageView
理论上是这样的,但我在这里看到很多关于 SO 的报道,它有点不可靠。正如 ohglstr 正确指出的那样,缓存 UIImage
并没有多大帮助,因为它仍然需要在每次使用时进行渲染。
我建议您使用 CIContext
自己渲染模糊图像并缓存结果。例如,您可以在 AssetManager
:
中执行此操作
final class AssetManager {
static let shared = AssetManager()
private var blurredBackground = [String: UIImage]()
private var ciContext: CIContext()
func blurred(named name: String) -> UIImage {
if let cachedImage = self.blurredBackground[name] {
return cachedImage
}
let ciImage = UIImage(named: name)!.blurred()!
let cgImage = self.ciContext.createCGImage(ciImage, from: ciImage.extent)!
let blurred = UIImage(cgImage: cgImage)
self.blurredBackground[name] = blurred
return blurred
}
}
首先,我想强调的是,根据 Firebase Crashlytics,此错误仅涉及 1% 的用户群。
我有一个包含许多 heic 图像的 xcasset 目录。 我需要显示一些这样的图像(原始版本)和一些模糊的图像。
这是加载和显示正常图像或模糊图像的代码。
// Original image
self.imageView.image = UIImage(named: "officeBackground")!
// Blurred image
self.imageView.image = AssetManager.shared.blurred(named: "officeBackground")
我使用管理器来缓存模糊图像,这样我就不必在每次显示它们时都重新生成它们。
final class AssetManager {
static let shared = AssetManager()
private var blurredBackground = [String: UIImage]()
func blurred(named: String) -> UIImage {
if let cachedImage = self.blurredBackground[from] {
return cachedImage
}
let blurred = UIImage(named: named)!.blurred()!
self.blurredBackground[from] = blurred
return blurred
}
}
最后是模糊代码
extension UIImage {
func blurred() -> UIImage? {
let ciimage: CIImage? = self.ciImage ?? CIImage(image: self)
guard let input = ciimage else { return nil }
let blurredImage = input.clampedToExtent()
.applyingFilter("CIGaussianBlur", parameters: [kCIInputRadiusKey: 13])
.cropped(to: input.extent)
return UIImage(ciImage: blurredImage, scale: self.scale, orientation: .up)
}
}
这是我遇到的两种崩溃类型
- 带有 CFAutorelease 的 CoreFoundation。 Crashlytics 有关于它的附加信息:
crash_info_entry_0:
*** CFAutorelease() called with NULL ***
- CoreImage 与 recursive_render。 Crashlytics 还有关于它的附加信息:
crash_info_entry_0:
Cache Stats: count=14 size=100MB non-volatile=0B peakCount=28 peakSize=199MB peakNVSize=50MB
我发现所有用户之间唯一的共同点是在崩溃时他们有 30 - 150 Mo 的 RAM(根据 Firebase,这个信息是否可靠?)。
说到这里,我真的是一头雾水。这似乎是 CoreImage / CoreFoundation 的一个错误,它如何处理内存中的 CIImage。
奇怪的是,因为我使用 AssetManager 来缓存模糊图像,我知道在崩溃期间用户已经在 RAM 中有可用的缓存版本,但是当设置 UIImageView 时缓存图像,它因内存不足而崩溃(?!)。为什么系统甚至试图分配内存来执行此操作?
根据我的经验,直接使用从 CIImage
创建的 UIImage
是非常不可靠和错误的。主要原因是CIImage
不是真正的位图图像,而是包含创建图像说明的收据 . UIImage
的消费者知道它由 CIImage
支持并正确呈现它。 UIImageView
理论上是这样的,但我在这里看到很多关于 SO 的报道,它有点不可靠。正如 ohglstr 正确指出的那样,缓存 UIImage
并没有多大帮助,因为它仍然需要在每次使用时进行渲染。
我建议您使用 CIContext
自己渲染模糊图像并缓存结果。例如,您可以在 AssetManager
:
final class AssetManager {
static let shared = AssetManager()
private var blurredBackground = [String: UIImage]()
private var ciContext: CIContext()
func blurred(named name: String) -> UIImage {
if let cachedImage = self.blurredBackground[name] {
return cachedImage
}
let ciImage = UIImage(named: name)!.blurred()!
let cgImage = self.ciContext.createCGImage(ciImage, from: ciImage.extent)!
let blurred = UIImage(cgImage: cgImage)
self.blurredBackground[name] = blurred
return blurred
}
}