简体   繁体   English

使用 CIImage 支持的 UIImage 设置 UIImageView 时发生罕见的崩溃

[英]Rare crashes when setting UIImageView with a UIImage backed with CIImage

First of all, I want to emphasize that this bug concerns only about 1% of the user base according to Firebase Crashlytics.首先,我想强调一下,根据 Firebase Crashlytics,这个错误只涉及大约1% 的用户群

I have a xcasset catalog with many heic images.我有一个包含许多 heic 图像的 xcasset 目录。 I need to display some of those images as such (original version) and some of them blurred.我需要显示其中一些图像(原始版本),其中一些是模糊的。

Here is the code to load and display a normal image or a blurred image.这是加载和显示正常图像或模糊图像的代码。

// Original image
self.imageView.image = UIImage(named: "officeBackground")!

// Blurred image
self.imageView.image = AssetManager.shared.blurred(named: "officeBackground")

I use a manager to cache the blurred images so that I don't have to re-generate them every time I display them.我使用管理器来缓存模糊的图像,这样我就不必每次显示它们时都重新生成它们。

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

And finally the blur code最后是模糊代码

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

And here are the 2 types of crashes I get这是我得到的两种类型的崩溃

  1. CoreFoundation with CFAutorelease. CoreFoundation 与 CFAutorelease。 Crashlytics has an additional info about it: Crashlytics 有关于它的附加信息:
crash_info_entry_0:
*** CFAutorelease() called with NULL ***

CoreFoundation CFAutorelease 堆栈跟踪崩溃

  1. CoreImage with recursive_render.带有 recursive_render 的 CoreImage。 Crashlytics has also this additional info about it: Crashlytics 也有关于它的附加信息:
crash_info_entry_0: 
Cache Stats: count=14 size=100MB non-volatile=0B peakCount=28 peakSize=199MB peakNVSize=50MB

CoreImage recursive_render 堆栈跟踪崩溃

The only common point I found between all users is that they have between 30 - 150 Mo of RAM at the time of crash (according to Firebase, if this info is even reliable?).我在所有用户之间发现的唯一共同点是他们在崩溃时拥有 30 - 150 Mo 的 RAM(根据 Firebase,如果这个信息甚至可靠吗?)。

At this point, I am honestly clueless.在这一点上,老实说,我一无所知。 It seems like a bug with CoreImage / CoreFoundation with how it handles CIImage in memory.它似乎是 CoreImage / CoreFoundation 的错误,它如何处理内存中的 CIImage。

The weird thing is that because I'm using the AssetManager to cache the blurred images, I know that during the time of crash the user already has a cache version available in RAM, and yet when setting the UIImageView with the cached image, it crashes because of low memory (?!).奇怪的是,因为我使用 AssetManager 来缓存模糊的图像,我知道在崩溃期间用户已经在 RAM 中有一个可用的缓存版本,但是当使用缓存的图像设置 UIImageView 时,它崩溃了因为内存不足(?!)。 Why is the system even trying to allocate memory to do this?为什么系统甚至试图分配内存来做到这一点?

In my experience, using a UIImage that is created from a CIImage directly is very unreliable and buggy.以我的经验,直接使用从CIImage创建的UIImage是非常不可靠和错误的。 The main reason is that a CIImage is not really a bitmap image, but rather a receipt that contains the instructions for creating an image.主要原因是CIImage并不是真正的位图图像,而是包含创建图像指令的收据 It is up to the consumer of the UIImage to know that it's backed by a CIImage and render it properly.UIImage的使用者知道它由CIImage支持并正确呈现它。 UIImageView theoretically does that, but I've seen many reports here on SO that it's somewhat unreliable. UIImageView理论上可以做到这一点,但我在这里看到很多关于 SO 的报告,它有点不可靠。 And as ohglstr correctly pointed out, caching that UIImage doesn't help much since it still needs to be rendered every time it's used.正如 ohglstr 正确指出的那样,缓存UIImage并没有多大帮助,因为每次使用它时仍然需要渲染它。

I recommend you use a CIContext to render the blurred images yourself and cache the result.我建议您使用CIContext自己渲染模糊图像并缓存结果。 You could for instance do that in your AssetManager :例如,您可以在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
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM