【问题标题】:Reduce memory consumption while loading gif images in UIImageView在 UIImageView 中加载 gif 图像时减少内存消耗
【发布时间】:2018-11-07 16:17:19
【问题描述】:

我想在 UIImageView 中显示 gif 图像,并使用下面的代码(来源:https://iosdevcenters.blogspot.com/2016/08/load-gif-image-in-swift_22.html,*我没有理解所有代码),我能够显示 gif 图像。但是,内存消耗似乎很高(在真实设备上测试)。有没有办法修改下面的代码以减少内存消耗?

@IBOutlet weak var imageView: UIImageView!

override func viewDidLoad() {
    super.viewDidLoad()
    let url =  "https://cdn-images-1.medium.com/max/800/1*oDqXedYUMyhWzN48pUjHyw.gif"
    let gifImage = UIImage.gifImageWithURL(url)
    imageView.image = gifImage 
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
    switch (lhs, rhs) {
    case let (l?, r?):
        return l < r
    case (nil, _?):
        return true
    default:
        return false
    }
}

extension UIImage {
   public class func gifImageWithData(_ data: Data) -> UIImage? {
        guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
            print("image doesn't exist")
            return nil
        }
            return UIImage.animatedImageWithSource(source)
    }

    public class func gifImageWithURL(_ gifUrl:String) -> UIImage? {
        guard let bundleURL:URL? = URL(string: gifUrl) else {
            return nil
        }
        guard let imageData = try? Data(contentsOf: bundleURL!) else {
            return nil
        }
        return gifImageWithData(imageData)
    }

    public class func gifImageWithName(_ name: String) -> UIImage? {
        guard let bundleURL = Bundle.main
        .url(forResource: name, withExtension: "gif") else {
            return nil
        }
        guard let imageData = try? Data(contentsOf: bundleURL) else {
            return nil
        }
        return gifImageWithData(imageData)
    }

    class func delayForImageAtIndex(_ index: Int, source: CGImageSource!) -> Double {
        var delay = 0.1
        let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil)
        let gifProperties: CFDictionary = unsafeBitCast(
        CFDictionaryGetValue(cfProperties,
                             Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque()),
        to: CFDictionary.self)

        var delayObject: AnyObject = unsafeBitCast(
        CFDictionaryGetValue(gifProperties,
                             Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()),
        to: AnyObject.self)
        if delayObject.doubleValue == 0 {
            delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties,
                                                         Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()), to: AnyObject.self)
        }

        delay = delayObject as! Double

        if delay < 0.1 {
            delay = 0.1
        }

        return delay
    }

    class func gcdForPair(_ a: Int?, _ b: Int?) -> Int {
        var a = a
        var b = b
        if b == nil || a == nil {
            if b != nil {
                return b!
            } else if a != nil {
                return a!
            } else {
                return 0
            }
        }

        if a < b {
            let c = a
            a = b
            b = c
        }

        var rest: Int
        while true {
            rest = a! % b!

            if rest == 0 {
               return b!
            } else {
                a = b
                b = rest
            }
        }
    }

    class func gcdForArray(_ array: Array<Int>) -> Int {
        if array.isEmpty {
            return 1
        }

        var gcd = array[0]

        for val in array {
            gcd = UIImage.gcdForPair(val, gcd)
        }

       return gcd
    }

    class func animatedImageWithSource(_ source: CGImageSource) -> UIImage? {
        let count = CGImageSourceGetCount(source)
        var images = [CGImage]()
        var delays = [Int]()

        for i in 0..<count {
            if let image = CGImageSourceCreateImageAtIndex(source, i, nil) {
                images.append(image)
            }

            let delaySeconds = UIImage.delayForImageAtIndex(Int(i),
                                                        source: source)
            delays.append(Int(delaySeconds * 1000.0)) // Seconds to ms
        }

        let duration: Int = {
            var sum = 0

            for val: Int in delays {
                sum += val
            }

            return sum
         }()

        let gcd = gcdForArray(delays)
        var frames = [UIImage]()

        var frame: UIImage
        var frameCount: Int
        for i in 0..<count {
            frame = UIImage(cgImage: images[Int(i)])
            frameCount = Int(delays[Int(i)] / gcd)

            for _ in 0..<frameCount {
                frames.append(frame)
            }
        }

        let animation = UIImage.animatedImage(with: frames,
                                          duration: Double(duration) / 1000.0)

        return animation
    }
}

当我将图像渲染为普通 png 图像时,消耗约为 10MB。

【问题讨论】:

  • 注释gif代码时使用了多少内存
  • @sanjaykmwt 我现在已经在真实设备中进行了测试,并更新了问题中两种情况的消耗。

标签: ios swift memory uiimageview gif


【解决方案1】:

相关 GIF 的分辨率为 480×288,包含 10 帧。

考虑到UIImageView 将帧存储为 4 字节 RGBA,此 GIF 在 RAM 中占用 4 × 10 × 480 × 288 = 5 529 600 字节,超过 5 兆字节。

有很多方法可以缓解这种情况,但只有一种方法不会对 CPU 造成额外压力;其他的只是 CPU 到 RAM 的权衡。

我所说的方法是继承UIImageView 并手动加载你的GIF,保留它们的内部表示(索引图像+调色板)。它可以让您将内存使用量减少四倍。

注意:尽管 GIF 可以存储为每一帧的完整图像(对于有问题的 GIF 来说就是这种情况),但很多都不是。相反,大多数帧只能包含自上一帧以来发生变化的像素。因此,一般来说,内部 GIF 表示只允许以直接顺序显示帧。

其他节省 RAM 的方法包括,例如在显示之前从磁盘重新读取每一帧,这肯定不利于电池寿命。

【讨论】:

  • 我现在正在使用一个名为 sdWebImage 的库,它提供 gif 支持。感谢您的解释。我想尝试您建议的方法,但我没有保留内部表示。如果你有一些代码可以分享,那就更好了。
  • @Sujal 没问题。考虑this loader。您可以使用任何您喜欢的 ObjC/C++ 编译器来构建它。传递给调用者定义的帧回调的GIF_WHDR 结构包含几乎完整的内部表示,只是以比原始 GIF 更方便的形式。如果您只存储唯一的调色板和部分索引图像,则可以节省大量 RAM。 (次要)缺点是您需要自己构建生成的图像,但这归结为复制上面的示例代码。
【解决方案2】:

要显示内存消耗较少的 GIF,请尝试BBWebImage

BBWebImage 将根据当前内存使用情况决定解码和缓存多少图像帧。如果空闲内存不够,只解码和缓存部分图像帧。

对于 Swift 4:

// BBAnimatedImageView (subclass UIImageView) displays animated image
imageView = BBAnimatedImageView(frame: frame)

// Load and display gif
imageView.bb_setImage(with: url,
                      placeholder: UIImage(named: "placeholder"))
{ (image: UIImage?, data: Data?, error: Error?, cacheType: BBImageCacheType) in
    // Do something when finish loading
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-13
    • 1970-01-01
    • 1970-01-01
    • 2023-03-18
    • 2015-05-10
    • 1970-01-01
    相关资源
    最近更新 更多