iOS:关于加载GIF图片的思考(2023-03-27更新)
前言
最近在项目中需要加入一个动画效果,设计师在导出Lottie动画后发现并不能达到效果,就想使用gif图片来实现。
但是将gif图片放入项目中运行时发现了一些问题,所以在这里整理一下有关加载gif图片的问题!
以下观点都是作者个人进行了不严谨的简单测试得出的结论,如有错误请多多包涵,欢迎讨论!
GIF原理
GIF的全称是Graphics Interchange Format,可译为图形交换格式,用于以超文本标志语言(Hypertext Markup Language)方式显示索引彩色图像,在因特网和其他在线服务系统上得到广泛应用。GIF是一种公用的图像文件格式标准,版权归Compu Serve公司所有。
详细去读GIF百度百科即可,我们不必深究。简单的将GIF理解为循环播放的幻灯片(个人见解)。
加载方式
- UIImageView
- SDWebImage
- QExtension
- WKWebView
- YYImage
- 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也可以说是很出名的一个三方了,它对UIimageView和UIImage都进行了很好的扩展,在播放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,即使进入后台依旧在占用线程。
在使用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(@"销毁了");
}
总结
- UIImageView
优点:原生
缺点:性能较差 - SDWebImage
优点:使用简单方便
缺点:需要引入三方、内存爆炸!!! - QExtension
优点:使用简单方便
缺点:需要引入三方、不常用、内存爆炸!!! - WKWebView
优点:原生、使用简单、兼容性好
缺点:无明显缺点 - YYImage
优点:使用简单、兼容性好,可以控制暂停和播放
缺点:需要引入三方,在后台依旧占用线程,需要自己解决一下。 - QMUI
优点:使用简单,本质就是NSdata转UIImage
缺点:需要引入三方、内存占用很高,无法自由控制播放
素材
学习资料
《iOS 客户端动图优化实践》- 原创 wyanwan 腾讯音乐技术团队 2023-05-09 12:02
《iOS播放GIF动画的几种方式》