Introduce frame pool for SDAnimatedImage playback. Solve when multiple image view references the same URL image cause un-wanted decode which waste RAM/CPU
This commit is contained in:
parent
3289629ef6
commit
a206229905
|
@ -45,6 +45,9 @@
|
|||
321E60C01F38E91700405457 /* UIImage+ForceDecode.h in Headers */ = {isa = PBXBuildFile; fileRef = 321E60BC1F38E91700405457 /* UIImage+ForceDecode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
321E60C41F38E91700405457 /* UIImage+ForceDecode.m in Sources */ = {isa = PBXBuildFile; fileRef = 321E60BD1F38E91700405457 /* UIImage+ForceDecode.m */; };
|
||||
321E60C61F38E91700405457 /* UIImage+ForceDecode.m in Sources */ = {isa = PBXBuildFile; fileRef = 321E60BD1F38E91700405457 /* UIImage+ForceDecode.m */; };
|
||||
3237321429F8D0D600D1DA41 /* SDImageFramePool.h in Headers */ = {isa = PBXBuildFile; fileRef = 3237321229F8D0D600D1DA41 /* SDImageFramePool.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
3237321529F8D0D600D1DA41 /* SDImageFramePool.m in Sources */ = {isa = PBXBuildFile; fileRef = 3237321329F8D0D600D1DA41 /* SDImageFramePool.m */; };
|
||||
3237321629F8D0E200D1DA41 /* SDImageFramePool.m in Sources */ = {isa = PBXBuildFile; fileRef = 3237321329F8D0D600D1DA41 /* SDImageFramePool.m */; };
|
||||
3237F9E820161AE000A88143 /* NSImage+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 4397D2F51D0DE2DF00BB2784 /* NSImage+Compatibility.m */; };
|
||||
3237F9EB20161AE000A88143 /* NSImage+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 4397D2F51D0DE2DF00BB2784 /* NSImage+Compatibility.m */; };
|
||||
3240BB6523968FA1003BA07D /* SDFileAttributeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.m */; };
|
||||
|
@ -407,6 +410,8 @@
|
|||
321E60A11F38E8F600405457 /* SDImageGIFCoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDImageGIFCoder.m; path = Core/SDImageGIFCoder.m; sourceTree = "<group>"; };
|
||||
321E60BC1F38E91700405457 /* UIImage+ForceDecode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+ForceDecode.h"; path = "Core/UIImage+ForceDecode.h"; sourceTree = "<group>"; };
|
||||
321E60BD1F38E91700405457 /* UIImage+ForceDecode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ForceDecode.m"; path = "Core/UIImage+ForceDecode.m"; sourceTree = "<group>"; };
|
||||
3237321229F8D0D600D1DA41 /* SDImageFramePool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDImageFramePool.h; sourceTree = "<group>"; };
|
||||
3237321329F8D0D600D1DA41 /* SDImageFramePool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDImageFramePool.m; sourceTree = "<group>"; };
|
||||
3240BB6623968FE6003BA07D /* SDAssociatedObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDAssociatedObject.h; sourceTree = "<group>"; };
|
||||
3240BB6723968FE6003BA07D /* SDAssociatedObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDAssociatedObject.m; sourceTree = "<group>"; };
|
||||
324406292296C5F400A36084 /* SDWebImageOptionsProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageOptionsProcessor.h; path = Core/SDWebImageOptionsProcessor.h; sourceTree = "<group>"; };
|
||||
|
@ -691,6 +696,8 @@
|
|||
325C460122339330004CAE11 /* SDImageAssetManager.m */,
|
||||
325C460C223394D8004CAE11 /* SDImageCachesManagerOperation.h */,
|
||||
325C460D223394D8004CAE11 /* SDImageCachesManagerOperation.m */,
|
||||
3237321229F8D0D600D1DA41 /* SDImageFramePool.h */,
|
||||
3237321329F8D0D600D1DA41 /* SDImageFramePool.m */,
|
||||
32C78E39233371AD00C6B7F8 /* SDImageIOAnimatedCoderInternal.h */,
|
||||
3253F235244982D3006C2BE8 /* SDWebImageTransitionInternal.h */,
|
||||
325C461E2233A02E004CAE11 /* UIColor+SDHexString.h */,
|
||||
|
@ -909,6 +916,7 @@
|
|||
32D3CDD121DDE87300C4DB49 /* UIImage+MemoryCacheCost.h in Headers */,
|
||||
328BB6AC2081FEE500760D6C /* SDWebImageCacheSerializer.h in Headers */,
|
||||
325F7CCA238942AB00AEDFCC /* UIImage+ExtendedCacheData.h in Headers */,
|
||||
3237321429F8D0D600D1DA41 /* SDImageFramePool.h in Headers */,
|
||||
325C46272233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h in Headers */,
|
||||
3253F236244982D3006C2BE8 /* SDWebImageTransitionInternal.h in Headers */,
|
||||
321B378F2083290E00C0EA77 /* SDImageLoadersManager.h in Headers */,
|
||||
|
@ -1188,6 +1196,7 @@
|
|||
32F21B5920788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.m in Sources */,
|
||||
321B37952083290E00C0EA77 /* SDImageLoadersManager.m in Sources */,
|
||||
4A2CAE361AB4BB7500B6BC39 /* UIImageView+WebCache.m in Sources */,
|
||||
3237321529F8D0D600D1DA41 /* SDImageFramePool.m in Sources */,
|
||||
4A2CAE1E1AB4BB6800B6BC39 /* SDWebImageDownloaderOperation.m in Sources */,
|
||||
3298655E2337230C0071958B /* SDImageHEICCoder.m in Sources */,
|
||||
32F7C0802030719600873181 /* UIImage+Transform.m in Sources */,
|
||||
|
@ -1262,6 +1271,7 @@
|
|||
32C0FDE72013426C001B8F2D /* SDWebImageIndicator.m in Sources */,
|
||||
32B5CC63222F8B70005EB74E /* SDAsyncBlockOperation.m in Sources */,
|
||||
32F21B5720788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.m in Sources */,
|
||||
3237321629F8D0E200D1DA41 /* SDImageFramePool.m in Sources */,
|
||||
5376130B155AD0D5005750A4 /* SDWebImageDownloader.m in Sources */,
|
||||
321B37932083290E00C0EA77 /* SDImageLoadersManager.m in Sources */,
|
||||
32F7C07E2030719600873181 /* UIImage+Transform.m in Sources */,
|
||||
|
|
|
@ -10,24 +10,24 @@
|
|||
#import "NSImage+Compatibility.h"
|
||||
#import "SDDisplayLink.h"
|
||||
#import "SDDeviceHelper.h"
|
||||
#import "SDImageFramePool.h"
|
||||
#import "SDInternalMacros.h"
|
||||
|
||||
@interface SDAnimatedImagePlayer () {
|
||||
SD_LOCK_DECLARE(_lock);
|
||||
// SD_LOCK_DECLARE(_lock);
|
||||
NSRunLoopMode _runLoopMode;
|
||||
}
|
||||
|
||||
@property (atomic, strong) SDImageFramePool *framePool;
|
||||
|
||||
@property (nonatomic, strong, readwrite) UIImage *currentFrame;
|
||||
@property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
|
||||
@property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
|
||||
@property (nonatomic, strong) id<SDAnimatedImageProvider> animatedProvider;
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer;
|
||||
@property (nonatomic, assign) NSTimeInterval currentTime;
|
||||
@property (nonatomic, assign) BOOL bufferMiss;
|
||||
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
|
||||
@property (nonatomic, assign) BOOL shouldReverse;
|
||||
@property (nonatomic, assign) NSUInteger maxBufferCount;
|
||||
@property (nonatomic, strong) NSOperationQueue *fetchQueue;
|
||||
@property (nonatomic, strong) SDDisplayLink *displayLink;
|
||||
|
||||
@end
|
||||
|
@ -47,7 +47,8 @@
|
|||
self.totalLoopCount = provider.animatedImageLoopCount;
|
||||
self.animatedProvider = provider;
|
||||
self.playbackRate = 1.0;
|
||||
SD_LOCK_INIT(_lock);
|
||||
self.framePool = [SDImageFramePool registerProvider:provider];
|
||||
// SD_LOCK_INIT(_lock);
|
||||
#if SD_UIKIT
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
||||
#endif
|
||||
|
@ -69,38 +70,24 @@
|
|||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
|
||||
[_fetchQueue cancelAllOperations];
|
||||
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
|
||||
NSNumber *currentFrameIndex = @(self.currentFrameIndex);
|
||||
SD_LOCK(self->_lock);
|
||||
NSArray *keys = self.frameBuffer.allKeys;
|
||||
// only keep the next frame for later rendering
|
||||
for (NSNumber * key in keys) {
|
||||
if (![key isEqualToNumber:currentFrameIndex]) {
|
||||
[self.frameBuffer removeObjectForKey:key];
|
||||
}
|
||||
}
|
||||
SD_UNLOCK(self->_lock);
|
||||
}];
|
||||
[_fetchQueue addOperation:operation];
|
||||
[self clearFrameBuffer];
|
||||
// [_fetchQueue cancelAllOperations];
|
||||
// NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
|
||||
// NSNumber *currentFrameIndex = @(self.currentFrameIndex);
|
||||
// SD_LOCK(self->_lock);
|
||||
// NSArray *keys = self.frameBuffer.allKeys;
|
||||
// // only keep the next frame for later rendering
|
||||
// for (NSNumber * key in keys) {
|
||||
// if (![key isEqualToNumber:currentFrameIndex]) {
|
||||
// [self.frameBuffer removeObjectForKey:key];
|
||||
// }
|
||||
// }
|
||||
// SD_UNLOCK(self->_lock);
|
||||
// }];
|
||||
// [_fetchQueue addOperation:operation];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
- (NSOperationQueue *)fetchQueue {
|
||||
if (!_fetchQueue) {
|
||||
_fetchQueue = [[NSOperationQueue alloc] init];
|
||||
_fetchQueue.maxConcurrentOperationCount = 1;
|
||||
_fetchQueue.name = @"com.hackemist.SDAnimatedImagePlayer.fetchQueue";
|
||||
}
|
||||
return _fetchQueue;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary<NSNumber *,UIImage *> *)frameBuffer {
|
||||
if (!_frameBuffer) {
|
||||
_frameBuffer = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return _frameBuffer;
|
||||
}
|
||||
|
||||
- (SDDisplayLink *)displayLink {
|
||||
if (!_displayLink) {
|
||||
|
@ -155,9 +142,10 @@
|
|||
if (posterFrame) {
|
||||
// HACK: The first frame should not check duration and immediately display
|
||||
self.needsDisplayWhenImageBecomesAvailable = YES;
|
||||
SD_LOCK(self->_lock);
|
||||
self.frameBuffer[@(self.currentFrameIndex)] = posterFrame;
|
||||
SD_UNLOCK(self->_lock);
|
||||
[self.framePool setFrame:posterFrame atIndex:self.currentFrameIndex];
|
||||
// SD_LOCK(self->_lock);
|
||||
// self.frameBuffer[@(self.currentFrameIndex)] = posterFrame;
|
||||
// SD_UNLOCK(self->_lock);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,9 +162,10 @@
|
|||
}
|
||||
|
||||
- (void)clearFrameBuffer {
|
||||
SD_LOCK(_lock);
|
||||
[_frameBuffer removeAllObjects];
|
||||
SD_UNLOCK(_lock);
|
||||
[self.framePool removeAllFrames];
|
||||
// SD_LOCK(_lock);
|
||||
// [_frameBuffer removeAllObjects];
|
||||
// SD_UNLOCK(_lock);
|
||||
}
|
||||
|
||||
#pragma mark - Animation Control
|
||||
|
@ -189,7 +178,7 @@
|
|||
}
|
||||
|
||||
- (void)stopPlaying {
|
||||
[_fetchQueue cancelAllOperations];
|
||||
// [_fetchQueue cancelAllOperations];
|
||||
// Using `_displayLink` here because when UIImageView dealloc, it may trigger `[self stopAnimating]`, we already release the display link in SDAnimatedImageView's dealloc method.
|
||||
[_displayLink stop];
|
||||
// We need to reset the frame status, but not trigger any handle. This can ensure next time's playing status correct.
|
||||
|
@ -197,7 +186,7 @@
|
|||
}
|
||||
|
||||
- (void)pausePlaying {
|
||||
[_fetchQueue cancelAllOperations];
|
||||
// [_fetchQueue cancelAllOperations];
|
||||
[_displayLink stop];
|
||||
}
|
||||
|
||||
|
@ -261,23 +250,26 @@
|
|||
// Check if we need to display new frame firstly
|
||||
BOOL bufferFull = NO;
|
||||
if (self.needsDisplayWhenImageBecomesAvailable) {
|
||||
UIImage *currentFrame;
|
||||
SD_LOCK(_lock);
|
||||
currentFrame = self.frameBuffer[@(currentFrameIndex)];
|
||||
SD_UNLOCK(_lock);
|
||||
UIImage *currentFrame = [self.framePool frameAtIndex:currentFrameIndex];
|
||||
// SD_LOCK(_lock);
|
||||
// currentFrame = self.frameBuffer[@(currentFrameIndex)];
|
||||
// SD_UNLOCK(_lock);
|
||||
|
||||
// Update the current frame
|
||||
if (currentFrame) {
|
||||
SD_LOCK(_lock);
|
||||
// Remove the frame buffer if need
|
||||
if (self.frameBuffer.count > self.maxBufferCount) {
|
||||
self.frameBuffer[@(currentFrameIndex)] = nil;
|
||||
}
|
||||
// Check whether we can stop fetch
|
||||
if (self.frameBuffer.count == totalFrameCount) {
|
||||
if (self.framePool.currentFrameCount == totalFrameCount) {
|
||||
bufferFull = YES;
|
||||
}
|
||||
SD_UNLOCK(_lock);
|
||||
// SD_LOCK(_lock);
|
||||
// // Remove the frame buffer if need
|
||||
// if (self.frameBuffer.count > self.maxBufferCount) {
|
||||
// self.frameBuffer[@(currentFrameIndex)] = nil;
|
||||
// }
|
||||
// // Check whether we can stop fetch
|
||||
// if (self.frameBuffer.count == totalFrameCount) {
|
||||
// bufferFull = YES;
|
||||
// }
|
||||
// SD_UNLOCK(_lock);
|
||||
|
||||
// Update the current frame immediately
|
||||
self.currentFrame = currentFrame;
|
||||
|
@ -351,31 +343,35 @@
|
|||
UIImage *fetchFrame = nil;
|
||||
if (!self.bufferMiss) {
|
||||
fetchFrameIndex = nextIndex;
|
||||
SD_LOCK(_lock);
|
||||
fetchFrame = self.frameBuffer[@(nextIndex)];
|
||||
SD_UNLOCK(_lock);
|
||||
fetchFrame = [self.framePool frameAtIndex:nextIndex];
|
||||
// SD_LOCK(_lock);
|
||||
// fetchFrame = self.frameBuffer[@(nextIndex)];
|
||||
// SD_UNLOCK(_lock);
|
||||
}
|
||||
if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
|
||||
// Prefetch next frame in background queue
|
||||
id<SDAnimatedImageProvider> animatedProvider = self.animatedProvider;
|
||||
@weakify(self);
|
||||
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
|
||||
@strongify(self);
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
UIImage *frame = [animatedProvider animatedImageFrameAtIndex:fetchFrameIndex];
|
||||
|
||||
BOOL isAnimating = self.displayLink.isRunning;
|
||||
if (isAnimating) {
|
||||
SD_LOCK(self->_lock);
|
||||
self.frameBuffer[@(fetchFrameIndex)] = frame;
|
||||
SD_UNLOCK(self->_lock);
|
||||
}
|
||||
}];
|
||||
[self.fetchQueue addOperation:operation];
|
||||
if (!fetchFrame && !bufferFull) {
|
||||
[self.framePool prefetchFrameAtIndex:fetchFrameIndex];
|
||||
}
|
||||
}
|
||||
// if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
|
||||
// // Prefetch next frame in background queue
|
||||
// id<SDAnimatedImageProvider> animatedProvider = self.animatedProvider;
|
||||
// @weakify(self);
|
||||
// NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
|
||||
// @strongify(self);
|
||||
// if (!self) {
|
||||
// return;
|
||||
// }
|
||||
// UIImage *frame = [animatedProvider animatedImageFrameAtIndex:fetchFrameIndex];
|
||||
//
|
||||
// BOOL isAnimating = self.displayLink.isRunning;
|
||||
// if (isAnimating) {
|
||||
// SD_LOCK(self->_lock);
|
||||
// self.frameBuffer[@(fetchFrameIndex)] = frame;
|
||||
// SD_UNLOCK(self->_lock);
|
||||
// }
|
||||
// }];
|
||||
// [self.fetchQueue addOperation:operation];
|
||||
// }
|
||||
|
||||
- (void)handleFrameChange {
|
||||
if (self.animationFrameHandler) {
|
||||
|
@ -410,7 +406,7 @@
|
|||
maxBufferCount = 1;
|
||||
}
|
||||
|
||||
self.maxBufferCount = maxBufferCount;
|
||||
self.framePool.maxBufferCount = maxBufferCount;
|
||||
}
|
||||
|
||||
+ (NSString *)defaultRunLoopMode {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "SDWebImageCompat.h"
|
||||
#import "SDImageCoder.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// A per-provider (provider means, AnimatedImage object) based frame pool, each player who use the same provider share the same frame buffer
|
||||
@interface SDImageFramePool : NSObject
|
||||
|
||||
/// Register and return back a frame pool, also increase reference count
|
||||
+ (instancetype)registerProvider:(id<SDAnimatedImageProvider>)provider;
|
||||
/// Unregister a frame pool, also decrease reference count, if zero dealloc the frame pool
|
||||
+ (void)unregisterProvider:(id<SDAnimatedImageProvider>)provider;
|
||||
|
||||
/// Prefetch the current frame, query using `frameAtIndex:` by caller to check whether finished.
|
||||
- (void)prefetchFrameAtIndex:(NSUInteger)index;
|
||||
|
||||
/// Control the max buffer count for current frame pool, used for RAM/CPU balance, default unlimited
|
||||
@property (nonatomic, assign) NSUInteger maxBufferCount;
|
||||
/// Control the max concurrent fetch queue operation count, used for CPU balance, default 1
|
||||
@property (nonatomic, assign) NSUInteger maxConcurrentCount;
|
||||
|
||||
// Frame Operations
|
||||
@property (nonatomic, readonly) NSUInteger currentFrameCount;
|
||||
- (nullable UIImage *)frameAtIndex:(NSUInteger)index;
|
||||
- (void)setFrame:(nullable UIImage *)frame atIndex:(NSUInteger)index;
|
||||
- (void)removeFrameAtIndex:(NSUInteger)index;
|
||||
- (void)removeAllFrames;
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@end
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import "SDImageFramePool.h"
|
||||
#import "SDInternalMacros.h"
|
||||
#import "objc/runtime.h"
|
||||
|
||||
@interface SDImageFramePool ()
|
||||
|
||||
@property (class, readonly) NSMapTable *providerFramePoolMap;
|
||||
|
||||
@property (weak) id<SDAnimatedImageProvider> provider;
|
||||
@property (atomic) NSUInteger registerCount;
|
||||
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer;
|
||||
@property (nonatomic, strong) NSOperationQueue *fetchQueue;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SDImageFramePool
|
||||
|
||||
+ (NSMapTable *)providerFramePoolMap {
|
||||
static NSMapTable *providerFramePoolMap;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
providerFramePoolMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality valueOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality];
|
||||
});
|
||||
return providerFramePoolMap;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_frameBuffer = [NSMutableDictionary dictionary];
|
||||
_fetchQueue = [[NSOperationQueue alloc] init];
|
||||
_fetchQueue.maxConcurrentOperationCount = 1;
|
||||
_fetchQueue.name = @"com.hackemist.SDImageFramePool.fetchQueue";
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)registerProvider:(id<SDAnimatedImageProvider>)provider {
|
||||
SDImageFramePool *framePool = [self.providerFramePoolMap objectForKey:provider];
|
||||
if (!framePool) {
|
||||
framePool = [[SDImageFramePool alloc] init];
|
||||
framePool.provider = provider;
|
||||
[self.providerFramePoolMap setObject:framePool forKey:provider];
|
||||
}
|
||||
framePool.registerCount += 1;
|
||||
return framePool;
|
||||
}
|
||||
|
||||
+ (void)unregisterProvider:(id<SDAnimatedImageProvider>)provider {
|
||||
SDImageFramePool *framePool = [self.providerFramePoolMap objectForKey:provider];
|
||||
if (!framePool) {
|
||||
return;
|
||||
}
|
||||
framePool.registerCount -= 1;
|
||||
if (framePool.registerCount == 0) {
|
||||
[self.providerFramePoolMap removeObjectForKey:provider];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)prefetchFrameAtIndex:(NSUInteger)index {
|
||||
@synchronized (self) {
|
||||
NSUInteger frameCount = self.frameBuffer.count;
|
||||
if (frameCount > self.maxBufferCount) {
|
||||
// Remove the frame buffer if need
|
||||
self.frameBuffer[@(index)] = nil;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.fetchQueue.operationCount == 0) {
|
||||
// Prefetch next frame in background queue
|
||||
id<SDAnimatedImageProvider> animatedProvider = self.provider;
|
||||
@weakify(self);
|
||||
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
|
||||
@strongify(self);
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
UIImage *frame = [animatedProvider animatedImageFrameAtIndex:index];
|
||||
|
||||
[self setFrame:frame atIndex:index];
|
||||
}];
|
||||
[self.fetchQueue addOperation:operation];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setMaxConcurrentCount:(NSUInteger)maxConcurrentCount {
|
||||
self.fetchQueue.maxConcurrentOperationCount = maxConcurrentCount;
|
||||
}
|
||||
|
||||
- (NSUInteger)currentFrameCount {
|
||||
NSUInteger frameCount = 0;
|
||||
@synchronized (self) {
|
||||
frameCount = self.frameBuffer.count;
|
||||
}
|
||||
return frameCount;
|
||||
}
|
||||
|
||||
- (void)setFrame:(UIImage *)frame atIndex:(NSUInteger)index {
|
||||
@synchronized (self) {
|
||||
self.frameBuffer[@(index)] = frame;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIImage *)frameAtIndex:(NSUInteger)index {
|
||||
UIImage *frame;
|
||||
@synchronized (self) {
|
||||
frame = self.frameBuffer[@(index)];
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
- (void)removeFrameAtIndex:(NSUInteger)index {
|
||||
@synchronized (self) {
|
||||
self.frameBuffer[@(index)] = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeAllFrames {
|
||||
@synchronized (self) {
|
||||
[self.frameBuffer removeAllObjects];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMutableDictionary<NSNumber *,UIImage *> *)frameBuffer {
|
||||
if (!_frameBuffer) {
|
||||
_frameBuffer = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return _frameBuffer;
|
||||
}
|
||||
|
||||
@end
|
Loading…
Reference in New Issue