Swift 项目总结 08 GIF 图片加载优化

一、问题出现

在公司项目中,需要显示一些网络 GIF 图片,使用的是 Kingfisher 第三方图片缓存库进行加载图片,一般情况下挺好的,但有时候会出现内存暴增,一开始以为是没有对图片缓存进行释放导致,后来测试发现是因为某个 GIF 帧数过高导致的,一个 1MB 大小但帧数有 150 帧的 GIF 图片,采用 Kingfisher 加载到内存中需要占用至少 300 MB 以上的内存,多加载几张这样的 GIF 内存直接爆炸,所以需要进行 GIF 图片加载进行优化。

二、问题思考

为什么会导致这样的内存暴增呢?

因为 Kingfisher 在加载 GIF 图的时候,会把 GIF 图的所有帧图片数据都加载到内存进行显示,导致内存暴增。

降低内存消耗,提高 CPU 消耗

去网上找第三方 GIF 图加载优化库,发现了SwiftGifYLGIFImage-Swift 这两个框架,我看了一下 YLGIFImage-Swift 框架里面的实现,是通过动态加载动画帧的形式来优化的。

动态加载帧原理:
  1. 一开始不加载所有图片帧,只加载少量的帧图片
  2. 在动画执行过程中利用定时器不断进行加载帧图片
  3. 释放已执行完动画的帧图片内存
  4. 内存消耗降低,这样的代价就是会导致 CPU 的使用提高

因为项目代码使用到的是 Swift3.2,YLGIFImage-Swift 第三方库更新比较慢,所以对该框架手动进行了一些调整和优化。

三、源代码解析和优化

String+MD5.swift 文件如下: 【需要桥接 OC 头文件 <CommonCrypto/CommonDigest.h>

// String+MD5.swift
import Foundation
extension String {
    /// 字符串 MD5 加密
    var encodeMD5: String? {
        guard let str = cString(using: String.Encoding.utf8) else { return nil }
        let strLen = CC_LONG(lengthOfBytes(using: String.Encoding.utf8))
        let digestLen = Int(CC_MD5_DIGEST_LENGTH)
        let result = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: digestLen)
        // MD5 加密
        CC_MD5(str, strLen, result)
        // 把结果打印输出成 16 进制字符串
        let hash = NSMutableString()
        for i in 0..<digestLen {
            hash.appendFormat("%02x", result[I])
        }
        result.deallocate(capacity: digestLen)
        return String(format: hash as String)
    }
}
复制代码

GIFImage.swift 文件如下:

// GIFImage.swift
import UIKit
import ImageIO
import MobileCoreServices

class GIFImage {
    /// 内部读取图片帧队列
    fileprivate lazy var readFrameQueue: DispatchQueue = DispatchQueue(label: "image.gif.readFrameQueue", qos: .background)
    /// 图片资源数据
    fileprivate var cgImageSource: CGImageSource?
    /// 总动画时长
    var totalDuration: TimeInterval = 0.0
    /// 每一帧对应的动画时长
    var frameDurations: [Int: TimeInterval] = [:]
    /// 每一帧对应的图片
    var frameImages: [Int: UIImage] = [:]
    /// 总图片数
    var frameTotalCount: Int = 0
    /// 兼容之前的 UIImage 使用
    var image: UIImage?

    /// 全局配置
    struct GlobalSetting {
        /// 配置预加载帧的数量
        static var prefetchNumber: Int = 10
        static var minFrameDuration: TimeInterval = 0.01
    }

    /// 兼容 UIImage named 调用
    convenience init?(named name: String!) {
        guard let path = Bundle.main.path(forResource: name, ofType: ".gif") else { return nil }
        guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { return nil }
        self.init(data: data)
    }

    /// 兼容 UIImage contentsOfFile 调用
    convenience init?(contentsOfFile path: String) {
        guard let url = URL(string: path) else { return nil }
        guard let data = try? Data(contentsOf: url) else { return nil }
        self.init(data: data)
    }
   
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值