一、简单的使用

if (!self.imageView1) {
    self.imageView1 = [[FLAnimatedImageView alloc] init];
    self.imageView1.contentMode = UIViewContentModeScaleAspectFill;
    self.imageView1.clipsToBounds = YES;
}
[self.view addSubview:self.imageView1];
self.imageView1.frame = CGRectMake(0.0, 120.0, self.view.bounds.size.width, 447.0);   
NSURL *url1 = [[NSBundle mainBundle] URLForResource:@"rock" withExtension:@"gif"];
NSData *data1 = [NSData dataWithContentsOfURL:url1];
FLAnimatedImage *animatedImage1 = [FLAnimatedImage animatedImageWithGIFData:data1];
self.imageView1.animatedImage = animatedImage1;

二、执行流程:

1:FLAnimatedImage到gif的data数据
2:生成CGImageSourceRef
3:获取GIF信息
4:获取GIF动画的帧图片
5:确认缓存策略
6:FLAnimatedImageView获取上边得到的image对象
7:使用CADisplayLink显示UIImage的动画内容

三、关键方法解析

1:获取ImageData数据

NSData * __block animatedImageData = [[NSFileManager defaultManager] contentsAtPath:@"filePath"];

2:基于ImageIO对gif动画数据进行解码

_imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)@{(NSString *)kCGImageSourceShouldCache: @NO});   

3:通过得到存放图片信息的字典,获取gif循环次数,和得到帧图像的个数

NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(_imageSource, NULL);
_loopCount = [[[imageProperties objectForKey:(id)kCGImagePropertyGIFDictionary] objectForKey:(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue]; 
size_t imageCount = CGImageSourceGetCount(_imageSource);

4:设置GIF的缓存策略

if (optimalFrameCacheSize == 0) {
    // Calculate the optimal frame cache size: try choosing a larger buffer window depending on the predicted image size.
    // It's only dependent on the image size & number of frames and never changes.
    CGFloat animatedImageDataSize = CGImageGetBytesPerRow(self.posterImage.CGImage) * self.size.height * (self.frameCount - skippedFrameCount) / MEGABYTE;
    if (animatedImageDataSize <= FLAnimatedImageDataSizeCategoryAll) {
        _frameCacheSizeOptimal = self.frameCount;
    } else if (animatedImageDataSize <= FLAnimatedImageDataSizeCategoryDefault) {
        // This value doesn't depend on device memory much because if we're not keeping all frames in memory we will always be decoding 1 frame up ahead per 1 frame that gets played and at this point we might as well just keep a small buffer just large enough to keep from running out of frames.
        _frameCacheSizeOptimal = FLAnimatedImageFrameCacheSizeDefault;
    } else {
        // The predicted size exceeds the limits to build up a cache and we go into low memory mode from the beginning.
        _frameCacheSizeOptimal = FLAnimatedImageFrameCacheSizeLowMemory;
    }
} else {
    // Use the provided value.
    _frameCacheSizeOptimal = optimalFrameCacheSize;
}

5:根据索引下标,获取每一帧的图片

UIImage *image = self.cachedFramesForIndexes[@(index)];
在使用ImageIO解析的时候,已经保存到cachedFramesForIndexes这个字典中;

6:在imageView上设置图片

// 封面图片
self.currentFrame = animatedImage.posterImage;
// 当前的帧图片索引
self.currentFrameIndex = 0;
// GIF动画的循环次数,不设置,则一直循环播放
if (animatedImage.loopCount > 0) {
    self.loopCountdown = animatedImage.loopCount;
} else {
    self.loopCountdown = NSUIntegerMax;
}
self.accumulator = 0.0;

7:播放GIF帧图片

// 解决循环引用问题
FLWeakProxy *weakProxy = [FLWeakProxy weakProxyForObject:self];
// CADisplayLink是一个定时器对象,它可以让你与屏幕刷新频率相同的速率来刷新你的视图
self.displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayDidRefresh:)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:self.runLoopMode];
// 当前需要展示的时间
NSNumber *delayTimeNumber = [self.animatedImage.delayTimesForIndexes objectForKey:@(self.currentFrameIndex)];
// 当前需要展示的图片
UIImage *image = [self.animatedImage imageLazilyCachedAtIndex:self.currentFrameIndex];
// 从前面的startAnimating方法中displayLink.frameInterval的计算过程可以知道,
// GIF动画中的帧图片的展示时间都是delayTime都是displayLink.duration * displayLink.frameInterval的倍数关系,
// 也就是说一个GIF动画帧图片的展示时间至少是一个display link fires的时间间隔。
// 以下数据是使用FLAnimatedImage的Demo项目的第一个GIF动画的播放信息打印出来的。
// 按照Demo中的打印数据来说,第0帧图片的展示时间是14个display link fires的时间间隔,而1,2,3帧图片都是只有一个display link fires的时间间隔。
// 所以累加器self.accumulator的意义在于累加display link fires的时间间隔,并与帧图片的delayTime做比较,如果小于delayTime说明该帧图片还需要继续展示,否则该帧图片结束展示。
while (self.accumulator >= delayTime) {
    self.accumulator -= delayTime;
    self.currentFrameIndex++;
    if (self.currentFrameIndex >= self.animatedImage.frameCount) {
        // If we've looped the number of times that this animated image describes, stop looping.
        self.loopCountdown--;
        if (self.loopCompletionBlock) {
            self.loopCompletionBlock(self.loopCountdown);
        }
        
        if (self.loopCountdown == 0) {
            [self stopAnimating];
            return;
        }
        self.currentFrameIndex = 0;
    }
    // Calling `-setNeedsDisplay` will just paint the current frame, not the new frame that we may have moved to.
    // Instead, set `needsDisplayWhenImageBecomesAvailable` to `YES` -- this will paint the new image once loaded.
    self.needsDisplayWhenImageBecomesAvailable = YES;
}