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:
DreamPiggy 2023-04-25 18:35:12 +08:00
parent 3289629ef6
commit a206229905
4 changed files with 263 additions and 77 deletions

View File

@ -45,6 +45,9 @@
321E60C01F38E91700405457 /* UIImage+ForceDecode.h in Headers */ = {isa = PBXBuildFile; fileRef = 321E60BC1F38E91700405457 /* UIImage+ForceDecode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 3237F9EB20161AE000A88143 /* NSImage+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 4397D2F51D0DE2DF00BB2784 /* NSImage+Compatibility.m */; };
3240BB6523968FA1003BA07D /* SDFileAttributeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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 */, 325C460122339330004CAE11 /* SDImageAssetManager.m */,
325C460C223394D8004CAE11 /* SDImageCachesManagerOperation.h */, 325C460C223394D8004CAE11 /* SDImageCachesManagerOperation.h */,
325C460D223394D8004CAE11 /* SDImageCachesManagerOperation.m */, 325C460D223394D8004CAE11 /* SDImageCachesManagerOperation.m */,
3237321229F8D0D600D1DA41 /* SDImageFramePool.h */,
3237321329F8D0D600D1DA41 /* SDImageFramePool.m */,
32C78E39233371AD00C6B7F8 /* SDImageIOAnimatedCoderInternal.h */, 32C78E39233371AD00C6B7F8 /* SDImageIOAnimatedCoderInternal.h */,
3253F235244982D3006C2BE8 /* SDWebImageTransitionInternal.h */, 3253F235244982D3006C2BE8 /* SDWebImageTransitionInternal.h */,
325C461E2233A02E004CAE11 /* UIColor+SDHexString.h */, 325C461E2233A02E004CAE11 /* UIColor+SDHexString.h */,
@ -909,6 +916,7 @@
32D3CDD121DDE87300C4DB49 /* UIImage+MemoryCacheCost.h in Headers */, 32D3CDD121DDE87300C4DB49 /* UIImage+MemoryCacheCost.h in Headers */,
328BB6AC2081FEE500760D6C /* SDWebImageCacheSerializer.h in Headers */, 328BB6AC2081FEE500760D6C /* SDWebImageCacheSerializer.h in Headers */,
325F7CCA238942AB00AEDFCC /* UIImage+ExtendedCacheData.h in Headers */, 325F7CCA238942AB00AEDFCC /* UIImage+ExtendedCacheData.h in Headers */,
3237321429F8D0D600D1DA41 /* SDImageFramePool.h in Headers */,
325C46272233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h in Headers */, 325C46272233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h in Headers */,
3253F236244982D3006C2BE8 /* SDWebImageTransitionInternal.h in Headers */, 3253F236244982D3006C2BE8 /* SDWebImageTransitionInternal.h in Headers */,
321B378F2083290E00C0EA77 /* SDImageLoadersManager.h in Headers */, 321B378F2083290E00C0EA77 /* SDImageLoadersManager.h in Headers */,
@ -1188,6 +1196,7 @@
32F21B5920788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.m in Sources */, 32F21B5920788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.m in Sources */,
321B37952083290E00C0EA77 /* SDImageLoadersManager.m in Sources */, 321B37952083290E00C0EA77 /* SDImageLoadersManager.m in Sources */,
4A2CAE361AB4BB7500B6BC39 /* UIImageView+WebCache.m in Sources */, 4A2CAE361AB4BB7500B6BC39 /* UIImageView+WebCache.m in Sources */,
3237321529F8D0D600D1DA41 /* SDImageFramePool.m in Sources */,
4A2CAE1E1AB4BB6800B6BC39 /* SDWebImageDownloaderOperation.m in Sources */, 4A2CAE1E1AB4BB6800B6BC39 /* SDWebImageDownloaderOperation.m in Sources */,
3298655E2337230C0071958B /* SDImageHEICCoder.m in Sources */, 3298655E2337230C0071958B /* SDImageHEICCoder.m in Sources */,
32F7C0802030719600873181 /* UIImage+Transform.m in Sources */, 32F7C0802030719600873181 /* UIImage+Transform.m in Sources */,
@ -1262,6 +1271,7 @@
32C0FDE72013426C001B8F2D /* SDWebImageIndicator.m in Sources */, 32C0FDE72013426C001B8F2D /* SDWebImageIndicator.m in Sources */,
32B5CC63222F8B70005EB74E /* SDAsyncBlockOperation.m in Sources */, 32B5CC63222F8B70005EB74E /* SDAsyncBlockOperation.m in Sources */,
32F21B5720788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.m in Sources */, 32F21B5720788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.m in Sources */,
3237321629F8D0E200D1DA41 /* SDImageFramePool.m in Sources */,
5376130B155AD0D5005750A4 /* SDWebImageDownloader.m in Sources */, 5376130B155AD0D5005750A4 /* SDWebImageDownloader.m in Sources */,
321B37932083290E00C0EA77 /* SDImageLoadersManager.m in Sources */, 321B37932083290E00C0EA77 /* SDImageLoadersManager.m in Sources */,
32F7C07E2030719600873181 /* UIImage+Transform.m in Sources */, 32F7C07E2030719600873181 /* UIImage+Transform.m in Sources */,

View File

@ -10,24 +10,24 @@
#import "NSImage+Compatibility.h" #import "NSImage+Compatibility.h"
#import "SDDisplayLink.h" #import "SDDisplayLink.h"
#import "SDDeviceHelper.h" #import "SDDeviceHelper.h"
#import "SDImageFramePool.h"
#import "SDInternalMacros.h" #import "SDInternalMacros.h"
@interface SDAnimatedImagePlayer () { @interface SDAnimatedImagePlayer () {
SD_LOCK_DECLARE(_lock); // SD_LOCK_DECLARE(_lock);
NSRunLoopMode _runLoopMode; NSRunLoopMode _runLoopMode;
} }
@property (atomic, strong) SDImageFramePool *framePool;
@property (nonatomic, strong, readwrite) UIImage *currentFrame; @property (nonatomic, strong, readwrite) UIImage *currentFrame;
@property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex; @property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
@property (nonatomic, assign, readwrite) NSUInteger currentLoopCount; @property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
@property (nonatomic, strong) id<SDAnimatedImageProvider> animatedProvider; @property (nonatomic, strong) id<SDAnimatedImageProvider> animatedProvider;
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer;
@property (nonatomic, assign) NSTimeInterval currentTime; @property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, assign) BOOL bufferMiss; @property (nonatomic, assign) BOOL bufferMiss;
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable; @property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
@property (nonatomic, assign) BOOL shouldReverse; @property (nonatomic, assign) BOOL shouldReverse;
@property (nonatomic, assign) NSUInteger maxBufferCount;
@property (nonatomic, strong) NSOperationQueue *fetchQueue;
@property (nonatomic, strong) SDDisplayLink *displayLink; @property (nonatomic, strong) SDDisplayLink *displayLink;
@end @end
@ -47,7 +47,8 @@
self.totalLoopCount = provider.animatedImageLoopCount; self.totalLoopCount = provider.animatedImageLoopCount;
self.animatedProvider = provider; self.animatedProvider = provider;
self.playbackRate = 1.0; self.playbackRate = 1.0;
SD_LOCK_INIT(_lock); self.framePool = [SDImageFramePool registerProvider:provider];
// SD_LOCK_INIT(_lock);
#if SD_UIKIT #if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif #endif
@ -69,38 +70,24 @@
} }
- (void)didReceiveMemoryWarning:(NSNotification *)notification { - (void)didReceiveMemoryWarning:(NSNotification *)notification {
[_fetchQueue cancelAllOperations]; [self clearFrameBuffer];
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ // [_fetchQueue cancelAllOperations];
NSNumber *currentFrameIndex = @(self.currentFrameIndex); // NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
SD_LOCK(self->_lock); // NSNumber *currentFrameIndex = @(self.currentFrameIndex);
NSArray *keys = self.frameBuffer.allKeys; // SD_LOCK(self->_lock);
// only keep the next frame for later rendering // NSArray *keys = self.frameBuffer.allKeys;
for (NSNumber * key in keys) { // // only keep the next frame for later rendering
if (![key isEqualToNumber:currentFrameIndex]) { // for (NSNumber * key in keys) {
[self.frameBuffer removeObjectForKey:key]; // if (![key isEqualToNumber:currentFrameIndex]) {
} // [self.frameBuffer removeObjectForKey:key];
} // }
SD_UNLOCK(self->_lock); // }
}]; // SD_UNLOCK(self->_lock);
[_fetchQueue addOperation:operation]; // }];
// [_fetchQueue addOperation:operation];
} }
#pragma mark - Private #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 { - (SDDisplayLink *)displayLink {
if (!_displayLink) { if (!_displayLink) {
@ -155,9 +142,10 @@
if (posterFrame) { if (posterFrame) {
// HACK: The first frame should not check duration and immediately display // HACK: The first frame should not check duration and immediately display
self.needsDisplayWhenImageBecomesAvailable = YES; self.needsDisplayWhenImageBecomesAvailable = YES;
SD_LOCK(self->_lock); [self.framePool setFrame:posterFrame atIndex:self.currentFrameIndex];
self.frameBuffer[@(self.currentFrameIndex)] = posterFrame; // SD_LOCK(self->_lock);
SD_UNLOCK(self->_lock); // self.frameBuffer[@(self.currentFrameIndex)] = posterFrame;
// SD_UNLOCK(self->_lock);
} }
} }
@ -174,9 +162,10 @@
} }
- (void)clearFrameBuffer { - (void)clearFrameBuffer {
SD_LOCK(_lock); [self.framePool removeAllFrames];
[_frameBuffer removeAllObjects]; // SD_LOCK(_lock);
SD_UNLOCK(_lock); // [_frameBuffer removeAllObjects];
// SD_UNLOCK(_lock);
} }
#pragma mark - Animation Control #pragma mark - Animation Control
@ -189,7 +178,7 @@
} }
- (void)stopPlaying { - (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. // 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]; [_displayLink stop];
// We need to reset the frame status, but not trigger any handle. This can ensure next time's playing status correct. // 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 { - (void)pausePlaying {
[_fetchQueue cancelAllOperations]; // [_fetchQueue cancelAllOperations];
[_displayLink stop]; [_displayLink stop];
} }
@ -261,23 +250,26 @@
// Check if we need to display new frame firstly // Check if we need to display new frame firstly
BOOL bufferFull = NO; BOOL bufferFull = NO;
if (self.needsDisplayWhenImageBecomesAvailable) { if (self.needsDisplayWhenImageBecomesAvailable) {
UIImage *currentFrame; UIImage *currentFrame = [self.framePool frameAtIndex:currentFrameIndex];
SD_LOCK(_lock); // SD_LOCK(_lock);
currentFrame = self.frameBuffer[@(currentFrameIndex)]; // currentFrame = self.frameBuffer[@(currentFrameIndex)];
SD_UNLOCK(_lock); // SD_UNLOCK(_lock);
// Update the current frame // Update the current frame
if (currentFrame) { if (currentFrame) {
SD_LOCK(_lock); if (self.framePool.currentFrameCount == totalFrameCount) {
// 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; 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 // Update the current frame immediately
self.currentFrame = currentFrame; self.currentFrame = currentFrame;
@ -351,31 +343,35 @@
UIImage *fetchFrame = nil; UIImage *fetchFrame = nil;
if (!self.bufferMiss) { if (!self.bufferMiss) {
fetchFrameIndex = nextIndex; fetchFrameIndex = nextIndex;
SD_LOCK(_lock); fetchFrame = [self.framePool frameAtIndex:nextIndex];
fetchFrame = self.frameBuffer[@(nextIndex)]; // SD_LOCK(_lock);
SD_UNLOCK(_lock); // fetchFrame = self.frameBuffer[@(nextIndex)];
// SD_UNLOCK(_lock);
} }
if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) { if (!fetchFrame && !bufferFull) {
// Prefetch next frame in background queue [self.framePool prefetchFrameAtIndex:fetchFrameIndex];
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.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 { - (void)handleFrameChange {
if (self.animationFrameHandler) { if (self.animationFrameHandler) {
@ -410,7 +406,7 @@
maxBufferCount = 1; maxBufferCount = 1;
} }
self.maxBufferCount = maxBufferCount; self.framePool.maxBufferCount = maxBufferCount;
} }
+ (NSString *)defaultRunLoopMode { + (NSString *)defaultRunLoopMode {

View File

@ -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

View File

@ -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