Files
keyboard/Pods/SDWebImage/SDWebImage/Private/SDImageFramePool.m
2025-10-27 21:55:05 +08:00

169 lines
5.0 KiB
Objective-C

/*
* 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
// Lock to ensure atomic behavior
SD_LOCK_DECLARE_STATIC(_providerFramePoolMapLock);
@implementation SDImageFramePool
+ (NSMapTable *)providerFramePoolMap {
static NSMapTable *providerFramePoolMap;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Key use `hash` && `isEqual:`
providerFramePoolMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality valueOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality];
});
return providerFramePoolMap;
}
#pragma mark - Life Cycle
- (instancetype)init {
self = [super init];
if (self) {
_frameBuffer = [NSMutableDictionary dictionary];
_fetchQueue = [[NSOperationQueue alloc] init];
_fetchQueue.maxConcurrentOperationCount = 1;
_fetchQueue.name = @"com.hackemist.SDImageFramePool.fetchQueue";
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
- (void)dealloc {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
[self removeAllFrames];
}
+ (void)initialize {
// Lock to ensure atomic behavior
SD_LOCK_INIT(_providerFramePoolMapLock);
}
+ (instancetype)registerProvider:(id<SDAnimatedImageProvider>)provider {
// Lock to ensure atomic behavior
SD_LOCK(_providerFramePoolMapLock);
SDImageFramePool *framePool = [self.providerFramePoolMap objectForKey:provider];
if (!framePool) {
framePool = [[SDImageFramePool alloc] init];
framePool.provider = provider;
[self.providerFramePoolMap setObject:framePool forKey:provider];
}
framePool.registerCount += 1;
SD_UNLOCK(_providerFramePoolMapLock);
return framePool;
}
+ (void)unregisterProvider:(id<SDAnimatedImageProvider>)provider {
// Lock to ensure atomic behavior
SD_LOCK(_providerFramePoolMapLock);
SDImageFramePool *framePool = [self.providerFramePoolMap objectForKey:provider];
if (!framePool) {
SD_UNLOCK(_providerFramePoolMapLock);
return;
}
framePool.registerCount -= 1;
if (framePool.registerCount == 0) {
[self.providerFramePoolMap removeObjectForKey:provider];
}
SD_UNLOCK(_providerFramePoolMapLock);
}
- (void)prefetchFrameAtIndex:(NSUInteger)index {
@synchronized (self) {
NSUInteger frameCount = self.frameBuffer.count;
if (frameCount > self.maxBufferCount) {
// Remove the frame buffer if need
// TODO, use LRU or better algorithm to detect which frames to clear
self.frameBuffer[@(index - 1)] = nil;
self.frameBuffer[@(index + 1)] = nil;
}
}
if (self.fetchQueue.operationCount == 0) {
// Prefetch next frame in background queue
@weakify(self);
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
@strongify(self);
if (!self) {
return;
}
id<SDAnimatedImageProvider> animatedProvider = self.provider;
if (!animatedProvider) {
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];
}
}
@end