在UIImageView中加载gif图像时减少内存消耗

时间:2018-05-29 05:44:07

标签: ios swift memory uiimageview gif

我想在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
    }
}

tested as gif

当我将图像渲染为普通的png图像时,消耗量约为10MB。 tested as non gif

2 个答案:

答案 0 :(得分:2)

有问题的GIF分辨率为480×288,包含10帧。

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

有许多方法可以缓解这种情况,但只有其中一种方法不会对CPU产生额外的压力;其他只是CPU到RAM之间的权衡。

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

N.B。:即使GIF可以存储为每帧的完整图像(对于所讨论的GIF也是如此),但很多都没有。相反,大多数帧只能包含自上一帧以来已更改的像素。因此,通常内部GIF表示仅允许以直接顺序显示帧。

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

答案 1 :(得分:-1)

要以较少的内存显示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
}