UIImage *image = [YYImage imageNamed:@"animation.gif"];
UIImageView *imageView = [[YYAnimatedImageView alloc] initWithImage:image];
[self.view addSubview:imageView];
// frame1.png, frame2.png, frame3.png三张图片
NSArray *paths = @[@"/ani/frame1.png", @"/ani/frame2.png", @"/ani/frame3.png"];
NSArray *times = @[@0.1, @0.2, @0.1];
UIImage *image = [YYFrameImage alloc] initWithImagePaths:paths frameDurations:times repeats:YES];
UIImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image];
[self.view addSubview:imageView];
// 8 * 12 sprites in a single sheet image
UIImage *spriteSheet = [UIImage imageNamed:@"sprite-sheet"];
NSMutableArray *contentRects = [NSMutableArray new];
NSMutableArray *durations = [NSMutableArray new];
for (int j = 0; j < 12; j++) {
for (int i = 0; i < 8; i++) {
CGRect rect;
rect.size = CGSizeMake(img.size.width / 8, img.size.height / 12);
rect.origin.x = img.size.width / 8 * i;
rect.origin.y = img.size.height / 12 * j;
[contentRects addObject:[NSValue valueWithCGRect:rect]];
[durations addObject:@(1 / 60.0)];
}
}
YYSpriteSheetImage *sprite;
sprite = [[YYSpriteSheetImage alloc] initWithSpriteSheetImage:img
contentRects:contentRects
frameDurations:durations
loopCount:0];
YYAnimatedImageView *imageView = [YYAnimatedImageView new];
imageView.size = CGSizeMake(img.size.width / 8, img.size.height / 12);
imageView.image = sprite;
[self.view addSubview:imageView];
// 1.初始化YYAnimatedImageView
YYAnimatedImageView *imageView = [[YYAnimatedImageView alloc] init];
imageView.backgroundColor = [UIColor whiteColor];
imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.view addSubview:imageView];
// 2.加载网络GIF图片
[imageView yy_setImageWithURL:[NSURL URLWithString:@"gif图url链接"] placeholder:[UIImage imageNamed:@"default"]];
// 3.通过RAC或者自己写观察者,观察currentAnimatedImageIndex播放到什么位置,如果播放到最后一张图,则停止播放
[RACObserve(imageView, currentAnimatedImageIndex) subscribeNext:^(id _Nullable x) {
if ([x integerValue] == imageView.animationImages.count) {
[_imageView stopAnimating];
}
}];
// 解码单帧图片
NSData *data = [NSData dataWithContentsOfFile:@"/tmp/image.webp"];
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:2.0];
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// 渐进式图片解码 (可用于图片下载显示)
NSMutableData *data = [NSMutableData new];
YYImageDecoder *decoder = [[YYImageDecoder alloc] initWithScale:2.0];
while(newDataArrived) {
[data appendData:newData];
[decoder updateData:data final:NO];
if (decoder.frameCount > 0) {
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// progressive display...
}
}
[decoder updateData:data final:YES];
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// 编码静态图 (支持各种常见图片格式):
YYImageEncoder *jpegEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeJPEG];
jpegEncoder.quality = 0.9;
[jpegEncoder addImage:image duration:0];
NSData jpegData = [jpegEncoder encode];
// 编码动态图 (支持 GIF/APNG/WebP):
YYImageEncoder *webpEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeWebP];
webpEncoder.loopCount = 5;
[webpEncoder addImage:image0 duration:0.1];
[webpEncoder addImage:image1 duration:0.15];
[webpEncoder addImage:image2 duration:0.2];
NSData webpData = [webpEncoder encode];
// 获取图片类型
YYImageType type = YYImageDetectType(data);
if (type == YYImageTypePNG) ...
YYImage.h (.m)
YYFrameImage.h (.m)
YYSpriteSheetImage.h (.m)
YYAnimatedImageView.h (.m)
YYImageCoder.h (.m)
- (YYImage *)imageNamed:(NSString *)name {
...
NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"];
NSArray *scales = _NSBundlePreferredScales();
for (int s = 0; s < scales.count; s++) {
scale = ((NSNumber *)scales[s]).floatValue;
NSString *scaledName = _NSStringByAppendingNameScale(res, scale);
for (NSString *e in exts) {
path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e];
if (path) break;
}
if (path) break;
}
...
return [[self alloc] initWithData:data scale:scale];
}
- (void)setPreloadAllAnimatedImageFrames:(BOOL)preloadAllAnimatedImageFrames {
if (_preloadAllAnimatedImageFrames != preloadAllAnimatedImageFrames) {
if (preloadAllAnimatedImageFrames && _decoder.frameCount > 0) {
NSMutableArray *frames = [NSMutableArray new];
//拿到所有帧的图片
for (NSUInteger i = 0, max = _decoder.frameCount; i < max; i++) {
UIImage *img = [self animatedImageFrameAtIndex:i];
[frames addObject:img ?: [NSNull null]];
}
dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
_preloadedFrames = frames;
dispatch_semaphore_signal(_preloadedLock);
} else {
dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
_preloadedFrames = nil;
dispatch_semaphore_signal(_preloadedLock);
}
}
}
- (nullable instancetype)initWithImagePaths:(NSArray<NSString *> *)paths
frameDurations:(NSArray<NSNumber *> *)frameDurations
loopCount:(NSUInteger)loopCount;
- (nullable instancetype)initWithImageDataArray:(NSArray<NSData *> *)dataArray
frameDurations:(NSArray *)frameDurations
loopCount:(NSUInteger)loopCount;
@property (nonatomic, readonly) NSArray<NSValue *> *contentRects;
@property (nonatomic, readonly) NSArray<NSValue *> *frameDurations;
@property (nonatomic, readonly) NSUInteger loopCount;
- (nullable instancetype)initWithSpriteSheetImage:(UIImage *)image
contentRects:(NSArray<NSValue *> *)contentRects
frameDurations:(NSArray<NSNumber *> *)frameDurations
loopCount:(NSUInteger)loopCount;
@protocol YYAnimatedImage <NSObject>
@required
//帧数量
- (NSUInteger)animatedImageFrameCount;
//动画循环次数
- (NSUInteger)animatedImageLoopCount;
//每帧在内存中的大小
- (NSUInteger)animatedImageBytesPerFrame;
//index 下标的帧图片
- (nullable UIImage *)animatedImageFrameAtIndex:(NSUInteger)index;
//index 下标帧图片持续时间
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index;
@optional
//index 下标帧图片的范围(CGRect)
- (CGRect)animatedImageContentsRectAtIndex:(NSUInteger)index;
@end
YYAnimatedImageView类通过YYImage、YYFrameImage、YYSpriteSheetImage实现的协议方法拿到帧图片数据和相关信息进行动画展示。原理如下:
- (void)setImage:(UIImage *)image {
if (self.image == image) return;
[self setImage:image withType:YYAnimatedImageTypeImage];
}
- (void)setHighlightedImage:(UIImage *)highlightedImage {
if (self.highlightedImage == highlightedImage) return;
[self setImage:highlightedImage withType:YYAnimatedImageTypeHighlightedImage];
}
...
- (void)imageChanged {
YYAnimatedImageType newType = [self currentImageType];
id newVisibleImage = [self imageForType:newType];
NSUInteger newImageFrameCount = 0;
BOOL hasContentsRect = NO;
... //省略判断是否是 SpriteSheet 类型来源
/*1、若上一次是 SpriteSheet 类型而当前显示的图片不是,则归位 self.layer.contentsRect */
if (!hasContentsRect && _curImageHasContentsRect) {
if (!CGRectEqualToRect(self.layer.contentsRect, CGRectMake(0, 0, 1, 1)) ) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
// 使用CATransaction事件来取消隐式动画
self.layer.contentsRect = CGRectMake(0, 0, 1, 1);
[CATransaction commit];
}
}
_curImageHasContentsRect = hasContentsRect;
/*2、SpriteSheet 类型时,通过`setContentsRect:forImage:`方法 配置self.layer.contentsRect */
if (hasContentsRect) {
CGRect rect = [((UIImage<YYAnimatedImage> *) newVisibleImage) animatedImageContentsRectAtIndex:0];
[self setContentsRect:rect forImage:newVisibleImage];
}
/*3、若是多帧的图片,通过`resetAnimated`方法初始化显示多帧动画需要的配置;然后拿到第一帧图片调用`setNeedsDisplay `绘制出来 */
if (newImageFrameCount > 1) {
[self resetAnimated];
_curAnimatedImage = newVisibleImage;
_curFrame = newVisibleImage;
_totalLoop = _curAnimatedImage.animatedImageLoopCount;
_totalFrameCount = _curAnimatedImage.animatedImageFrameCount;
[self calcMaxBufferCount];
}
[self setNeedsDisplay];
[self didMoved];
}
- (void)didMoved {
if (self.autoPlayAnimatedImage) {
if(self.superview && self.window) {
[self startAnimating];
} else {
[self stopAnimating];
}
}
}
- (void)didMoveToWindow {
[super didMoveToWindow];
[self didMoved];
}
- (void)didMoveToSuperview {
[super didMoveToSuperview];
[self didMoved];
}
_requestQueue = [[NSOperationQueue alloc] init];
_requestQueue.maxConcurrentOperationCount = 1;
- (void)main {
...
for (int i = 0; i < max; i++, idx++) {
@autoreleasepool {
...
if (miss) {
UIImage *img = [_curImage animatedImageFrameAtIndex:idx];
img = img.yy_imageByDecoded;
if ([self isCancelled]) break;
LOCK_VIEW(view->_buffer[@(idx)] = img ? img : [NSNull null]);
view = nil;
}
}
}
}
NSMutableDictionary *_buffer; ///< frame buffer
BOOL _bufferMiss; ///< whether miss frame on last opportunity
NSUInteger _maxBufferCount; ///< maximum buffer count
NSInteger _incrBufferCount; ///< current allowed buffer count (will increase by step)
- (void)calcMaxBufferCount {
int64_t bytes = (int64_t)_curAnimatedImage.animatedImageBytesPerFrame;
if (bytes == 0) bytes = 1024;
int64_t total = _YYDeviceMemoryTotal();
int64_t free = _YYDeviceMemoryFree();
int64_t max = MIN(total * 0.2, free * 0.6);
max = MAX(max, BUFFER_SIZE);
if (_maxBufferSize) max = max > _maxBufferSize ? _maxBufferSize : max;
double maxBufferCount = (double)max / (double)bytes;
if (maxBufferCount < 1) maxBufferCount = 1;
else if (maxBufferCount > 512) maxBufferCount = 512;
_maxBufferCount = maxBufferCount;
}
// resetAnimated中的监听
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
// 内存警告或者APP进入后台时,修改缓存
- (void)didEnterBackground:(NSNotification *)notification {
[_requestQueue cancelAllOperations];
NSNumber *next = @((_curIndex + 1) % _totalFrameCount);
LOCK(
NSArray * keys = _buffer.allKeys;
for (NSNumber * key in keys) {
if (![key isEqualToNumber:next]) {
// keep the next frame for smoothly animation
[_buffer removeObjectForKey:key];
}
}
)//LOCK
}
@interface _YYImageWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
...
@end
...
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
,,,
该文件中主要包含了YYImageFrame图片帧信息的类、YYImageDecoder解码器、YYImageEncoder编码器。
CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay) {
...
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
BOOL hasAlpha = NO;
if (alphaInfo == kCGImageAlphaPremultipliedLast ||
alphaInfo == kCGImageAlphaPremultipliedFirst ||
alphaInfo == kCGImageAlphaLast ||
alphaInfo == kCGImageAlphaFirst) {
hasAlpha = YES;
}
// BGRA8888 (premultiplied) or BGRX8888
// same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo);
if (!context) return NULL;
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
CGImageRef newImage = CGBitmapContextCreateImage(context);
CFRelease(context);
return newImage;
...
}
// 它接受一个原始的位图参数 imageRef ,最终返回一个新的解压缩后的位图 newImage
CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay) {
if (!imageRef) return NULL;
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
if (width == 0 || height == 0) return NULL;
// 重新绘制解码(可能会失去一些精度)
if (decodeForDisplay) {
//decode with redraw (may lose some precision)
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
BOOL hasAlpha = NO;
if (alphaInfo == kCGImageAlphaPremultipliedLast ||
alphaInfo == kCGImageAlphaPremultipliedFirst ||
alphaInfo == kCGImageAlphaLast ||
alphaInfo == kCGImageAlphaFirst) {
hasAlpha = YES;
}
// BGRA8888 (premultiplied) or BGRX8888
// same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo);
if (!context) return NULL;
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
CGImageRef newImage = CGBitmapContextCreateImage(context);
CFRelease(context);
return newImage; // 返回一个新的解压缩后的位图 newImage
} else {
}
}
- (void)imageChanged {
YYAnimatedImageType newType = [self currentImageType];
id newVisibleImage = [self imageForType:newType];
NSUInteger newImageFrameCount = 0;
BOOL hasContentsRect = NO;
... //省略判断是否是 SpriteSheet 类型来源
/*1、若上一次是 SpriteSheet 类型而当前显示的图片不是,则归位 self.layer.contentsRect */
if (!hasContentsRect && _curImageHasContentsRect) {
if (!CGRectEqualToRect(self.layer.contentsRect, CGRectMake(0, 0, 1, 1)) ) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
// 使用CATransaction事件来取消隐式动画
self.layer.contentsRect = CGRectMake(0, 0, 1, 1);
[CATransaction commit];
}
}
_curImageHasContentsRect = hasContentsRect;
/*2、SpriteSheet 类型时,通过`setContentsRect:forImage:`方法 配置self.layer.contentsRect */
if (hasContentsRect) {
CGRect rect = [((UIImage<YYAnimatedImage> *) newVisibleImage) animatedImageContentsRectAtIndex:0];
[self setContentsRect:rect forImage:newVisibleImage];
}
/*3、若是多帧的图片,通过`resetAnimated`方法初始化显示多帧动画需要的配置;然后拿到第一帧图片调用`setNeedsDisplay `绘制出来 */
if (newImageFrameCount > 1) {
[self resetAnimated];
_curAnimatedImage = newVisibleImage;
_curFrame = newVisibleImage;
_totalLoop = _curAnimatedImage.animatedImageLoopCount;
_totalFrameCount = _curAnimatedImage.animatedImageFrameCount;
[self calcMaxBufferCount];
}
[self setNeedsDisplay];
[self didMoved];
}
// init the animated params.
- (void)resetAnimated {
if (!_link) {
_lock = dispatch_semaphore_create(1);
_buffer = [NSMutableDictionary new];
// 添加到这种队列中的操作,就会自动放到子线程中执行。
_requestQueue = [[NSOperationQueue alloc] init];
/* maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
为1时,队列为串行队列。只能串行执行。大于1时,队列为并发队列 */
_requestQueue.maxConcurrentOperationCount = 1;
/* 初始化一个新的 CADisplayLink 对象,在屏幕更新时调用。为了使显示循环与显示同步,应用程序使用addToRunLoop:forMode:方法将其添加到运行循环中
一个计时器对象,允许应用程序将其绘图同步到显示的刷新率。
*/
_link = [CADisplayLink displayLinkWithTarget:[_YYImageWeakProxy proxyWithTarget:self] selector:@selector(step:)];
if (_runloopMode) {
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode];
}
// 禁用通知
_link.paused = YES;
// 接受内存警告的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
// 接受返回后台的通知,返回后台时,记录即将显示的下一帧
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
[_requestQueue cancelAllOperations];
LOCK(
if (_buffer.count) {
NSMutableDictionary *holder = _buffer;
_buffer = [NSMutableDictionary new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Capture the dictionary to global queue,
// release these images in background to avoid blocking UI thread.
[holder class]; // 捕获字典到全局队列,在后台释放这些图像以避免阻塞UI线程。
});
}
);
_link.paused = YES;
_time = 0;
if (_curIndex != 0) {
[self willChangeValueForKey:@"currentAnimatedImageIndex"];
_curIndex = 0; // 把索引值重置为0
[self didChangeValueForKey:@"currentAnimatedImageIndex"];
}
_curAnimatedImage = nil; // 当前图像为空
_curFrame = nil; // 当前帧
_curLoop = 0; //当前循环次数
_totalLoop = 0; // 总循环次数
_totalFrameCount = 1; // 总帧数
_loopEnd = NO; // 是否循环结尾
_bufferMiss = NO; // 是否丢帧
_incrBufferCount = 0; // 当前允许的缓存
}
// 只有屏幕刷新累加时间不小于当前帧的动画播放时间才显示图片,播放下一帧。
// 播放 GIF 的关键
- (void)step:(CADisplayLink *)link {
UIImage <YYAnimatedImage> *image = _curAnimatedImage;
NSMutableDictionary *buffer = _buffer;
// 下一张的图片
UIImage *bufferedImage = nil;
// 下一张要显示的索引
NSUInteger nextIndex = (_curIndex + 1) % _totalFrameCount;
BOOL bufferIsFull = NO;
// // 当前无图像显示 返回
if (!image) return;
if (_loopEnd) {
// view will keep in last frame // 结束循环 停留在最后帧
[self stopAnimating]; // 如果动画播放循环结束了,就停止动画
return;
}
NSTimeInterval delay = 0;
if (!_bufferMiss) {
// 屏幕刷新时间的累加
_time += link.duration; // link.duration 屏幕刷新的时间,默认1/60 s
delay = [image animatedImageDurationAtIndex:_curIndex]; // 返回当前帧的持续时间
if (_time < delay) return;
_time -= delay; // 减去上一帧播放的时间
if (nextIndex == 0) {
_curLoop++; // 增加一轮循环次数
if (_curLoop >= _totalLoop && _totalLoop != 0) {
// 已经到了循环次数,停止播放
_loopEnd = YES;
[self stopAnimating];
[self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep
return; // stop at last frame
}
}
delay = [image animatedImageDurationAtIndex:nextIndex]; // 返回下一帧的的持续时间
/** */
if (_time > delay) _time = delay; // do not jump over frame
}
LOCK(
bufferedImage = buffer[@(nextIndex)];
if (bufferedImage) {
if ((int)_incrBufferCount < _totalFrameCount) {
[buffer removeObjectForKey:@(nextIndex)];
}
[self willChangeValueForKey:@"currentAnimatedImageIndex"];
_curIndex = nextIndex; // 用KVO改变 当前索引值
[self didChangeValueForKey:@"currentAnimatedImageIndex"];
_curFrame = bufferedImage == (id)[NSNull null] ? nil : bufferedImage;
// 实现YYSpriteSheetImage 的协议方法,才会进入该 if 语句
if (_curImageHasContentsRect) {
_curContentsRect = [image animatedImageContentsRectAtIndex:_curIndex];
[self setContentsRect:_curContentsRect forImage:_curFrame];
}
nextIndex = (_curIndex + 1) % _totalFrameCount;
_bufferMiss = NO;
if (buffer.count == _totalFrameCount) {
bufferIsFull = YES; // 缓冲区已经满
}
} else {
// 丢帧,某一帧没有办法找到显示
_bufferMiss = YES;
}
)//LOCK
if (!_bufferMiss) {
// 刷新显示图像
[self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep
}
/* _YYAnimatedImageViewFetchOperation 为 NSOperation 的子类
还未获取完所有图像,交给它获取下一张图像 */
if (!bufferIsFull && _requestQueue.operationCount == 0) {
// if some work not finished, wait for next opportunity
_YYAnimatedImageViewFetchOperation *operation = [_YYAnimatedImageViewFetchOperation new];
operation.view = self;
operation.nextIndex = nextIndex;
operation.curImage = image;
[_requestQueue addOperation:operation]; //
}
}
- (void)calcMaxBufferCount {
int64_t bytes = (int64_t)_curAnimatedImage.animatedImageBytesPerFrame; // 求出每一帧的字节数
if (bytes == 0) bytes = 1024; // 如果为0,则给定1024
int64_t total = _YYDeviceMemoryTotal(); // 获取设备的CPU物理内存
int64_t free = _YYDeviceMemoryFree(); // 获取设备的容量
int64_t max = MIN(total * 0.2, free * 0.6); // 比较内存的0.2倍以及容量的0.6倍最小值
max = MAX(max, BUFFER_SIZE); // 如果不够 10 M,则以 10 M 作为最大缓冲区大小
/** _maxBufferSize 内部帧缓冲区大小
* 当设备有足够的空闲内存时,这个视图将请求并解码一些或所有未来的帧图像进入一个内部缓冲区。
* 默认值为0 如果这个属性的值是0,那么最大缓冲区大小将根据当前的状态进行动态调整设备释放内存。否则,缓冲区大小将受到此值的限制。
* 当收到内存警告或应用程序进入后台时,缓冲区将被立即释放
*/
if (_maxBufferSize) max = max > _maxBufferSize ? _maxBufferSize : max; //得出缓冲区的最大值
double maxBufferCount = (double)max / (double)bytes;
if (maxBufferCount < 1) maxBufferCount = 1;
else if (maxBufferCount > 512) maxBufferCount = 512;
_maxBufferCount = maxBufferCount; // 最大缓冲数
}
/* 从自定义的 start 方法中调用 main 方法
调用[self didMoved]; 从而调用此方法
*/
- (void)main {
__strong YYAnimatedImageView *view = _view;
if (!view) return;
if ([self isCancelled]) return;
view->_incrBufferCount++;
//动态调整当前内存的缓冲区大小。
if (view->_incrBufferCount == 0) [view calcMaxBufferCount];
if (view->_incrBufferCount > (NSInteger)view->_maxBufferCount) {
view->_incrBufferCount = view->_maxBufferCount;
}
NSUInteger idx = _nextIndex; // 获取 Operation 中传过来的 下一个索引值
NSUInteger max = view->_incrBufferCount < 1 ? 1 : view->_incrBufferCount; // 当前的缓冲区计数
NSUInteger total = view->_totalFrameCount; // 总图片帧数
view = nil;
for (int i = 0; i < max; i++, idx++) {
@autoreleasepool {
if (idx >= total) idx = 0;
if ([self isCancelled]) break;
__strong YYAnimatedImageView *view = _view;
if (!view) break;
LOCK_VIEW(BOOL miss = (view->_buffer[@(idx)] == nil)); // 拿索引值去当前缓冲区取图片
// 如果没有取到图片,则在子线程重新解码,得到解码后的图片
if (miss) {
// 等到当前还未解码的图片
UIImage *img = [_curImage animatedImageFrameAtIndex:idx];
NSLog(@"当前线程---%@", [NSThread currentThread]); // 打印当前线程,每次打印都是 name = (null),说明在异步线程
// 在异步线程再次调用解码图片,如果无法解码或已经解码就返回self
img = img.yy_imageByDecoded;
if ([self isCancelled]) break;
LOCK_VIEW(view->_buffer[@(idx)] = img ? img : [NSNull null]); // 每次添加一张图片到 _buffer 数组
view = nil;
}
}
}
}