iOS:关于加载GIF图片的思考

前言

最近在项目中需要加入一个动画效果,设计师在导出Lottie动画后发现并不能达到效果,就想使用gif图片来实现。
但是将gif图片放入项目中运行时发现了一些问题,所以在这里整理一下有关加载gif图片的问题!
以下观点都是作者个人进行了不严谨的简单测试得出的结论,如有错误请多多包涵,欢迎讨论!

GIF原理

GIF的全称是Graphics Interchange Format,可译为图形交换格式,用于以超文本标志语言(Hypertext Markup Language)方式显示索引彩色图像,在因特网和其他在线服务系统上得到广泛应用。GIF是一种公用的图像文件格式标准,版权归Compu Serve公司所有。
详细去读GIF百度百科即可,我们不必深究。简单的将GIF理解为循环播放的幻灯片(个人见解)。

加载方式

  1. UIImageView
  2. SDWebImage
  3. QExtension
  4. WKWebView
  5. YYImage
  6. QMUI
    就以下6种方式进行讨论,根据实际需求情况选择显示方案,我个人还是推荐使用YYimage,具体如下:

UIImageView

系统提供的UIimageView是支持多张图片的播放的,类似于播放幻灯片,但是这样就需要先将*.gif*文件先进行抽帧为单独的帧图片,再进行播放,很麻烦!但是实现确实很简单的,如果只是少数几张图片的切换的话,还是可以选择的。

#pragma mark - eg
UIImageView *gifImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 400, 300)];
gifImageView.animationImages = @[];   // 存放每一帧的UIImage的数组
gifImageView.animationDuration = 5;   // 执行一次完整动画所需的时长
gifImageView.animationRepeatCount = 1;// 动画重复次数
[gifImageView startAnimating];
[self.view addSubView:gifImageView];
#pragma mark - UIImageView Api
@property (nonatomic, getter=isHighlighted) BOOL highlighted API_AVAILABLE(ios(3.0)); // default is NO
// these allow a set of images to be animated. the array may contain multiple copies of the same
@property (nullable, nonatomic, copy) NSArray<UIImage *> *animationImages; // The array must contain UIImages. Setting hides the single image. default is nil
@property (nullable, nonatomic, copy) NSArray<UIImage *> *highlightedAnimationImages API_AVAILABLE(ios(3.0)); // The array must contain UIImages. Setting hides the single image. default is nil
@property (nonatomic) NSTimeInterval animationDuration;         // for one cycle of images. default is number of images * 1/30th of a second (i.e. 30 fps)
@property (nonatomic) NSInteger      animationRepeatCount;      // 0 means infinite (default is 0)
// When tintColor is non-nil, any template images set on the image view will be colorized with that color.
// The tintColor is inherited through the superview hierarchy. See UIView for more information.
@property (null_resettable, nonatomic, strong) UIColor *tintColor API_AVAILABLE(ios(7.0));
- (void)startAnimating;
- (void)stopAnimating;
@property(nonatomic, readonly, getter=isAnimating) BOOL animating;



    NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:@"<#gifName#>" withExtension:@"gif"]; //加载GIF图片
    CGImageSourceRef gifSource = CGImageSourceCreateWithURL((CFURLRef) fileUrl, NULL);           //将GIF图片转换成对应的图片源
    size_t frameCout = CGImageSourceGetCount(gifSource);                                         //获取其中图片源个数,即由多少帧图片组成
    NSMutableArray *frames = [[NSMutableArray alloc] init];                                      //定义数组存储拆分出来的图片
    for (size_t i = 0; i < frameCout; i++) {
    
    
        CGImageRef imageRef = CGImageSourceCreateImageAtIndex(gifSource, i, NULL); //从GIF图片中取出源图片
        UIImage *imageName = [UIImage imageWithCGImage:imageRef];                  //将图片源转换成UIimageView能使用的图片源
        [frames addObject:imageName];                                              //将图片加入数组中
        CGImageRelease(imageRef);
    }
    UIImageView *gifImageView = [[UIImageView alloc] initWithFrame:CGRectMake(<#x#>, <#y#>, <#w#>, <#h#>)];
    gifImageView.animationImages = frames; //将图片数组加入UIImageView动画数组中
    gifImageView.animationDuration = 0.15; //每次动画时长
    [gifImageView startAnimating];         //开启动画,此处没有调用播放次数接口,UIImageView默认播放次数为无限次,故这里不做处理
    [self.view addSubview:gifImageView];

SDWebImage

SDWebImage可以说是加载图片的常用三方库了,尝试着使用了一下,但是却发现了很严重的内存占用问题。加载一次GIF在高帧数的情况下内存直接暴涨了近200M有的100多M,这肯定是不推荐使用了!

#import <SDWebImage/UIImage+GIF.h>
#pragma mark - eg
// 注意
UIImageView *gifImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 400, 300)];
NSString *filePath = [[NSBundle bundleWithPath:[[NSBundle mainBundle] bundlePath]] pathForResource:@"test" ofType:@"gif"];
NSData *imageData = [NSData dataWithContentsOfFile:filePath];
gifImageView.image = [UIImage sd_imageWithGIFData:imageData];
[self.view addSubView:gifImageView];

QExtension

QExtension是一个对很多类进行了拓展的三方,提供了很多方法,但是只有较少人使用,同样在使用时发生了和SDWebImage一样的内存问题,所以并不推荐使用!

#import <QExtension/UIImage+GIF.h>
#pragma mark - eg
// 注意
UIImageView *gifImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 400, 300)];
NSString *filePath = [[NSBundle bundleWithPath:[[NSBundle mainBundle] bundlePath]] pathForResource:@"test" ofType:@"gif"];
NSData *imageData = [NSData dataWithContentsOfFile:filePath];
gifImageView.image = [UIImage q_gifImageWithData:imageData];
[self.view addSubView:gifImageView];

WKWebView

系统的WKWebView也是可以对GIF的数据进行展示的,而且在内存方面就友好很多,不会暴涨,我在测试时 涨了7M。使用起来也很简单,只是少量的需求,且不想引入更多三方的话,可以考虑使用!

#import <WebKit/WebKit.h>
#pragma mark - eg
WKWebView *gifView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 400, 300)];
NSString *filePath = [[NSBundle bundleWithPath:[[NSBundle mainBundle] bundlePath]] pathForResource:@"test" ofType:@"gif"];
NSData *imageData = [NSData dataWithContentsOfFile:filePath];
[gifView loadData:imageData MIMEType:@"image/gif" characterEncodingName:@"" baseURL:[NSURL URLWithString:@""]];
[self.view addSubView:gifView];

YYImage

YYImage也可以说是很出名的一个三方了,它对UIimageViewUIImage都进行了很好的扩展,在播放GIF图片上的优化也是做的很好!由于是对UIimageView的扩展,它具有所有UIimageView的特性,兼容性很好!我在测试时内存涨了6M

#import <YYImage/YYImage.h>
#pragma mark - eg
UIImageView *gifImageView = [[YYAnimatedImageView alloc] initWithFrame:CGRectMake(0, 0, 400, 300)];
UIImage *img = [YYImage imageNamed:@"test.gif"];
gifImageView.image = img;
[self.view addSubView:gifImageView];

GIF图片直接放在Bundle里就可以了!

QMUI

腾讯开发的QMUI库也提供了加载GIF的方法,经过想学习发现内部实现是这样的:

+ (UIImage *)qmui_animatedImageWithData:(NSData *)data scale:(CGFloat)scale {
    
    
    // http://www.jianshu.com/p/767af9c690a3
    // https://github.com/rs/SDWebImage
    if (!data) {
    
    
        return nil;
    }
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    size_t count = CGImageSourceGetCount(source);
    UIImage *animatedImage = nil;
    scale = scale == 0 ? ScreenScale : scale;
    if (count <= 1) {
    
    
        animatedImage = [[UIImage alloc] initWithData:data scale:scale];
    } else {
    
    
        NSMutableArray<UIImage *> *images = [[NSMutableArray alloc] init];
        NSTimeInterval duration = 0.0f;
        for (size_t i = 0; i < count; i++) {
    
    
            CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
            duration += [self qmui_frameDurationAtIndex:i source:source];
            UIImage *frameImage = [UIImage imageWithCGImage:image scale:scale orientation:UIImageOrientationUp];
            [images addObject:frameImage];
            CGImageRelease(image);
        }
        if (!duration) {
    
    
            duration = (1.0f / 10.0f) * count;
        }
        animatedImage = [UIImage animatedImageWithImages:images duration:duration];
    }
    CFRelease(source);
    return animatedImage;
}

其本质还是使用NSData的方法加载UIImage。

简单的优化

在2023年3月25日,进行Timer Profiler检查时,发现YYImage显示GIF,即使进入后台依旧在占用线程。
timer profiler
在使用QMUI进行同一张GIF加载对比后发现
对比
QMUI对于加载时间和进入后台后的表现优于YYImage

#import <QMUIKit/QMUIKit.h>
#pragma mark - eg
UIImageView *gifImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)];
gifImageView.image = [UIImage qmui_animatedImageNamed:@"test.gif"];
[self.view addSubView:gifImageView];

但是,内存方面,YYImage却大大的优于QMUI
内存比较
对于进入后台后,YYAnimatedImageView一直在运行的解决方案:
使用进入前后台的监听方式,在进入后台时暂停动画的播放,在进入前台后再播放。

- (void)applicationDidBecomeActive{
    
    
    [self.gifImageView startAnimating];
}

- (void)applicationDidEnterBackground{
    
    
    [self.gifImageView stopAnimating];
}

- (void)viewDidLoad {
    
    
    [super viewDidLoad];
    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(applicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(applicationDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
    [super viewDidLoad];
    self.view.backgroundColor = UIColor.grayColor;
    [self.view addSubview:self.gifImageView];
}

- (UIImageView *)gifImageView {
    
    
    if (!_gifImageView) {
    
    
        _gifImageView = [[YYAnimatedImageView alloc] init];
        _gifImageView.frame = CGRectMake(0, 0, 200, 200);
        _gifImageView.center = self.view.center;
        _gifImageView.image = [YYImage imageNamed:@"418k.gif"];
    }
    return _gifImageView;
}

- (void)dealloc {
    
    
    [NSNotificationCenter.defaultCenter removeObserver:self];
    NSLog(@"销毁了");
}

解决方法

总结

  1. UIImageView
    优点:原生
    缺点:性能较差
  2. SDWebImage
    优点:使用简单方便
    缺点:需要引入三方、内存爆炸!!!
  3. QExtension
    优点:使用简单方便
    缺点:需要引入三方、不常用、内存爆炸!!!
  4. WKWebView
    优点:原生、使用简单、兼容性好
    缺点:无明显缺点
  5. YYImage
    优点:使用简单、兼容性好,可以控制暂停和播放
    缺点:需要引入三方,在后台依旧占用线程,需要自己解决一下。
  6. QMUI
    优点:使用简单,本质就是NSdata转UIImage
    缺点:需要引入三方、内存占用很高,无法自由控制播放

素材

1
2
3

学习资料

《iOS 客户端动图优化实践》- 原创 wyanwan 腾讯音乐技术团队 2023-05-09 12:02

《iOS播放GIF动画的几种方式》

猜你喜欢

转载自blog.csdn.net/qq_38718912/article/details/123399925