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

561 lines
22 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 "SDImageCachesManager.h"
#import "SDImageCachesManagerOperation.h"
#import "SDImageCache.h"
#import "SDInternalMacros.h"
@interface SDImageCachesManager ()
@property (nonatomic, strong, nonnull) NSMutableArray<id<SDImageCache>> *imageCaches;
@end
@implementation SDImageCachesManager {
SD_LOCK_DECLARE(_cachesLock);
}
+ (SDImageCachesManager *)sharedManager {
static dispatch_once_t onceToken;
static SDImageCachesManager *manager;
dispatch_once(&onceToken, ^{
manager = [[SDImageCachesManager alloc] init];
});
return manager;
}
- (instancetype)init {
self = [super init];
if (self) {
self.queryOperationPolicy = SDImageCachesManagerOperationPolicySerial;
self.storeOperationPolicy = SDImageCachesManagerOperationPolicyHighestOnly;
self.removeOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent;
self.containsOperationPolicy = SDImageCachesManagerOperationPolicySerial;
self.clearOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent;
// initialize with default image caches
_imageCaches = [NSMutableArray arrayWithObject:[SDImageCache sharedImageCache]];
SD_LOCK_INIT(_cachesLock);
}
return self;
}
- (NSArray<id<SDImageCache>> *)caches {
SD_LOCK(_cachesLock);
NSArray<id<SDImageCache>> *caches = [_imageCaches copy];
SD_UNLOCK(_cachesLock);
return caches;
}
- (void)setCaches:(NSArray<id<SDImageCache>> *)caches {
SD_LOCK(_cachesLock);
[_imageCaches removeAllObjects];
if (caches.count) {
[_imageCaches addObjectsFromArray:caches];
}
SD_UNLOCK(_cachesLock);
}
#pragma mark - Cache IO operations
- (void)addCache:(id<SDImageCache>)cache {
if (![cache conformsToProtocol:@protocol(SDImageCache)]) {
return;
}
SD_LOCK(_cachesLock);
[_imageCaches addObject:cache];
SD_UNLOCK(_cachesLock);
}
- (void)removeCache:(id<SDImageCache>)cache {
if (![cache conformsToProtocol:@protocol(SDImageCache)]) {
return;
}
SD_LOCK(_cachesLock);
[_imageCaches removeObject:cache];
SD_UNLOCK(_cachesLock);
}
#pragma mark - SDImageCache
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context completion:(SDImageCacheQueryCompletionBlock)completionBlock {
return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:completionBlock];
}
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock {
if (!key) {
return nil;
}
NSArray<id<SDImageCache>> *caches = self.caches;
NSUInteger count = caches.count;
if (count == 0) {
return nil;
} else if (count == 1) {
return [caches.firstObject queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
switch (self.queryOperationPolicy) {
case SDImageCachesManagerOperationPolicyHighestOnly: {
id<SDImageCache> cache = caches.lastObject;
return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyLowestOnly: {
id<SDImageCache> cache = caches.firstObject;
return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyConcurrent: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
[self concurrentQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
return operation;
}
break;
case SDImageCachesManagerOperationPolicySerial: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
[self serialQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
return operation;
}
break;
default:
return nil;
break;
}
}
- (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:imageData forKey:key options:0 context:nil cacheType:cacheType completion:completionBlock];
}
- (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock {
if (!key) {
return;
}
NSArray<id<SDImageCache>> *caches = self.caches;
NSUInteger count = caches.count;
if (count == 0) {
return;
} else if (count == 1) {
[caches.firstObject storeImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:completionBlock];
return;
}
switch (self.storeOperationPolicy) {
case SDImageCachesManagerOperationPolicyHighestOnly: {
id<SDImageCache> cache = caches.lastObject;
[cache storeImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyLowestOnly: {
id<SDImageCache> cache = caches.firstObject;
[cache storeImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyConcurrent: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
[self concurrentStoreImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
}
break;
case SDImageCachesManagerOperationPolicySerial: {
[self serialStoreImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator];
}
break;
default:
break;
}
}
- (void)removeImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock {
if (!key) {
return;
}
NSArray<id<SDImageCache>> *caches = self.caches;
NSUInteger count = caches.count;
if (count == 0) {
return;
} else if (count == 1) {
[caches.firstObject removeImageForKey:key cacheType:cacheType completion:completionBlock];
return;
}
switch (self.removeOperationPolicy) {
case SDImageCachesManagerOperationPolicyHighestOnly: {
id<SDImageCache> cache = caches.lastObject;
[cache removeImageForKey:key cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyLowestOnly: {
id<SDImageCache> cache = caches.firstObject;
[cache removeImageForKey:key cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyConcurrent: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
[self concurrentRemoveImageForKey:key cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
}
break;
case SDImageCachesManagerOperationPolicySerial: {
[self serialRemoveImageForKey:key cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator];
}
break;
default:
break;
}
}
- (void)containsImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheContainsCompletionBlock)completionBlock {
if (!key) {
return;
}
NSArray<id<SDImageCache>> *caches = self.caches;
NSUInteger count = caches.count;
if (count == 0) {
return;
} else if (count == 1) {
[caches.firstObject containsImageForKey:key cacheType:cacheType completion:completionBlock];
return;
}
switch (self.clearOperationPolicy) {
case SDImageCachesManagerOperationPolicyHighestOnly: {
id<SDImageCache> cache = caches.lastObject;
[cache containsImageForKey:key cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyLowestOnly: {
id<SDImageCache> cache = caches.firstObject;
[cache containsImageForKey:key cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyConcurrent: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
[self concurrentContainsImageForKey:key cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
}
break;
case SDImageCachesManagerOperationPolicySerial: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
[self serialContainsImageForKey:key cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
}
break;
default:
break;
}
}
- (void)clearWithCacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock {
NSArray<id<SDImageCache>> *caches = self.caches;
NSUInteger count = caches.count;
if (count == 0) {
return;
} else if (count == 1) {
[caches.firstObject clearWithCacheType:cacheType completion:completionBlock];
return;
}
switch (self.clearOperationPolicy) {
case SDImageCachesManagerOperationPolicyHighestOnly: {
id<SDImageCache> cache = caches.lastObject;
[cache clearWithCacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyLowestOnly: {
id<SDImageCache> cache = caches.firstObject;
[cache clearWithCacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyConcurrent: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
[self concurrentClearWithCacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
}
break;
case SDImageCachesManagerOperationPolicySerial: {
[self serialClearWithCacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator];
}
break;
default:
break;
}
}
#pragma mark - Concurrent Operation
- (void)concurrentQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
NSParameterAssert(enumerator);
NSParameterAssert(operation);
for (id<SDImageCache> cache in enumerator) {
[cache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
if (operation.isCancelled) {
// Cancelled
return;
}
if (operation.isFinished) {
// Finished
return;
}
[operation completeOne];
if (image) {
// Success
[operation done];
if (completionBlock) {
completionBlock(image, data, cacheType);
}
return;
}
if (operation.pendingCount == 0) {
// Complete
[operation done];
if (completionBlock) {
completionBlock(nil, nil, SDImageCacheTypeNone);
}
}
}];
}
}
- (void)concurrentStoreImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
NSParameterAssert(enumerator);
NSParameterAssert(operation);
for (id<SDImageCache> cache in enumerator) {
[cache storeImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:^{
if (operation.isCancelled) {
// Cancelled
return;
}
if (operation.isFinished) {
// Finished
return;
}
[operation completeOne];
if (operation.pendingCount == 0) {
// Complete
[operation done];
if (completionBlock) {
completionBlock();
}
}
}];
}
}
- (void)concurrentRemoveImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
NSParameterAssert(enumerator);
NSParameterAssert(operation);
for (id<SDImageCache> cache in enumerator) {
[cache removeImageForKey:key cacheType:cacheType completion:^{
if (operation.isCancelled) {
// Cancelled
return;
}
if (operation.isFinished) {
// Finished
return;
}
[operation completeOne];
if (operation.pendingCount == 0) {
// Complete
[operation done];
if (completionBlock) {
completionBlock();
}
}
}];
}
}
- (void)concurrentContainsImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheContainsCompletionBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
NSParameterAssert(enumerator);
NSParameterAssert(operation);
for (id<SDImageCache> cache in enumerator) {
[cache containsImageForKey:key cacheType:cacheType completion:^(SDImageCacheType containsCacheType) {
if (operation.isCancelled) {
// Cancelled
return;
}
if (operation.isFinished) {
// Finished
return;
}
[operation completeOne];
if (containsCacheType != SDImageCacheTypeNone) {
// Success
[operation done];
if (completionBlock) {
completionBlock(containsCacheType);
}
return;
}
if (operation.pendingCount == 0) {
// Complete
[operation done];
if (completionBlock) {
completionBlock(SDImageCacheTypeNone);
}
}
}];
}
}
- (void)concurrentClearWithCacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
NSParameterAssert(enumerator);
NSParameterAssert(operation);
for (id<SDImageCache> cache in enumerator) {
[cache clearWithCacheType:cacheType completion:^{
if (operation.isCancelled) {
// Cancelled
return;
}
if (operation.isFinished) {
// Finished
return;
}
[operation completeOne];
if (operation.pendingCount == 0) {
// Complete
[operation done];
if (completionBlock) {
completionBlock();
}
}
}];
}
}
#pragma mark - Serial Operation
- (void)serialQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
NSParameterAssert(enumerator);
NSParameterAssert(operation);
id<SDImageCache> cache = enumerator.nextObject;
if (!cache) {
// Complete
[operation done];
if (completionBlock) {
completionBlock(nil, nil, SDImageCacheTypeNone);
}
return;
}
@weakify(self);
[cache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
@strongify(self);
if (operation.isCancelled) {
// Cancelled
return;
}
if (operation.isFinished) {
// Finished
return;
}
[operation completeOne];
if (image) {
// Success
[operation done];
if (completionBlock) {
completionBlock(image, data, cacheType);
}
return;
}
// Next
[self serialQueryImageForKey:key options:options context:context cacheType:queryCacheType completion:completionBlock enumerator:enumerator operation:operation];
}];
}
- (void)serialStoreImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator {
NSParameterAssert(enumerator);
id<SDImageCache> cache = enumerator.nextObject;
if (!cache) {
// Complete
if (completionBlock) {
completionBlock();
}
return;
}
@weakify(self);
[cache storeImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:^{
@strongify(self);
// Next
[self serialStoreImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:enumerator];
}];
}
- (void)serialRemoveImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator {
NSParameterAssert(enumerator);
id<SDImageCache> cache = enumerator.nextObject;
if (!cache) {
// Complete
if (completionBlock) {
completionBlock();
}
return;
}
@weakify(self);
[cache removeImageForKey:key cacheType:cacheType completion:^{
@strongify(self);
// Next
[self serialRemoveImageForKey:key cacheType:cacheType completion:completionBlock enumerator:enumerator];
}];
}
- (void)serialContainsImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheContainsCompletionBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
NSParameterAssert(enumerator);
NSParameterAssert(operation);
id<SDImageCache> cache = enumerator.nextObject;
if (!cache) {
// Complete
[operation done];
if (completionBlock) {
completionBlock(SDImageCacheTypeNone);
}
return;
}
@weakify(self);
[cache containsImageForKey:key cacheType:cacheType completion:^(SDImageCacheType containsCacheType) {
@strongify(self);
if (operation.isCancelled) {
// Cancelled
return;
}
if (operation.isFinished) {
// Finished
return;
}
[operation completeOne];
if (containsCacheType != SDImageCacheTypeNone) {
// Success
[operation done];
if (completionBlock) {
completionBlock(containsCacheType);
}
return;
}
// Next
[self serialContainsImageForKey:key cacheType:cacheType completion:completionBlock enumerator:enumerator operation:operation];
}];
}
- (void)serialClearWithCacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator {
NSParameterAssert(enumerator);
id<SDImageCache> cache = enumerator.nextObject;
if (!cache) {
// Complete
if (completionBlock) {
completionBlock();
}
return;
}
@weakify(self);
[cache clearWithCacheType:cacheType completion:^{
@strongify(self);
// Next
[self serialClearWithCacheType:cacheType completion:completionBlock enumerator:enumerator];
}];
}
@end