Merge pull request #3657 from dreampiggy/bugfix/thumbnail_query_full_data_never_store_thumbnail_into_memory

Fix the behavior that query thumbnail from full size data does not sync back the thumbnail image into memory cache
This commit is contained in:
DreamPiggy 2024-01-10 14:11:08 +08:00 committed by GitHub
commit 3eb8dd1439
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 66 additions and 37 deletions

View File

@ -15,6 +15,15 @@
#import "UIImage+Metadata.h"
#import "UIImage+ExtendedCacheData.h"
#import "SDCallbackQueue.h"
#import "SDImageTransformer.h" // TODO, remove this
// TODO, remove this
static BOOL SDIsThumbnailKey(NSString *key) {
if ([key rangeOfString:@"-Thumbnail("].location != NSNotFound) {
return YES;
}
return NO;
}
@interface SDImageCacheToken ()
@ -430,22 +439,9 @@ static NSString * _defaultDiskCacheDirectory;
SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
}
CGSize thumbnailSize = CGSizeZero;
NSValue *thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
if (thumbnailSizeValue != nil) {
#if SD_MAC
thumbnailSize = thumbnailSizeValue.sizeValue;
#else
thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
}
if (thumbnailSize.width > 0 && thumbnailSize.height > 0) {
// Query full size cache key which generate a thumbnail, should not write back to full size memory cache
shouldCacheToMomery = NO;
}
if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
if (shouldCacheToMomery) {
// check if we need sync logic
[self _syncDiskToMemoryWithImage:diskImage forKey:key];
}
return diskImage;
@ -526,6 +522,43 @@ static NSString * _defaultDiskCacheDirectory;
return image;
}
- (void)_syncDiskToMemoryWithImage:(UIImage *)diskImage forKey:(NSString *)key {
// earily check
if (!self.config.shouldCacheImagesInMemory) {
return;
}
if (!diskImage) {
return;
}
// The disk -> memory sync logic, which should only store thumbnail image with thumbnail key
// However, caller (like SDWebImageManager) will query full key, with thumbnail size, and get thubmnail image
// We should add a check here, currently it's a hack
if (diskImage.sd_isThumbnail) {
NSAssert(!SDIsThumbnailKey(key), @"The input cache key %@ should not be thumbnail key", key);
SDImageCoderOptions *options = diskImage.sd_decodeOptions;
CGSize thumbnailSize = CGSizeZero;
NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
if (thumbnailSizeValue != nil) {
#if SD_MAC
thumbnailSize = thumbnailSizeValue.sizeValue;
#else
thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
}
BOOL preserveAspectRatio = YES;
NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
if (preserveAspectRatioValue != nil) {
preserveAspectRatio = preserveAspectRatioValue.boolValue;
}
// Calculate the actual thumbnail key
NSString *thumbnailKey = SDThumbnailedKeyForKey(key, thumbnailSize, preserveAspectRatio);
// Override the sync key
key = thumbnailKey;
}
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
}
- (void)_unarchiveObjectWithImage:(UIImage *)image forKey:(NSString *)key {
if (!image || !key) {
return;
@ -655,19 +688,6 @@ static NSString * _defaultDiskCacheDirectory;
SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
}
CGSize thumbnailSize = CGSizeZero;
NSValue *thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
if (thumbnailSizeValue != nil) {
#if SD_MAC
thumbnailSize = thumbnailSizeValue.sizeValue;
#else
thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
}
if (thumbnailSize.width > 0 && thumbnailSize.height > 0) {
// Query full size cache key which generate a thumbnail, should not write back to full size memory cache
shouldCacheToMomery = NO;
}
// Special case: If user query image in list for the same URL, to avoid decode and write **same** image object into disk cache multiple times, we query and check memory cache here again.
if (shouldCacheToMomery && self.config.shouldCacheImagesInMemory) {
diskImage = [self.memoryCache objectForKey:key];
@ -675,9 +695,9 @@ static NSString * _defaultDiskCacheDirectory;
// decode image data only if in-memory cache missed
if (!diskImage) {
diskImage = [self diskImageForKey:key data:diskData options:options context:context];
if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
// check if we need sync logic
if (shouldCacheToMomery) {
[self _syncDiskToMemoryWithImage:diskImage forKey:key];
}
}
}

View File

@ -302,8 +302,13 @@ static id<SDImageLoader> _defaultImageLoader;
if (shouldQueryCache) {
// transformed cache key
NSString *key = [self cacheKeyForURL:url context:context];
// to avoid the SDImageCache's sync logic use the mismatched cache key
// we should strip the `thumbnail` related context
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextImageThumbnailPixelSize] = nil;
mutableContext[SDWebImageContextImagePreserveAspectRatio] = nil;
@weakify(operation);
operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
operation.cacheOperation = [imageCache queryImageForKey:key options:options context:mutableContext cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
@strongify(operation);
if (!operation || operation.isCancelled) {
// Image combined operation cancelled by user

View File

@ -482,11 +482,15 @@
[SDImageCache.sharedImageCache queryCacheOperationForKey:fullSizeKey options:0 context:@{SDWebImageContextImageThumbnailPixelSize : @(thumbnailSize)} done:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
expect(image.size).equal(thumbnailSize);
expect(cacheType).equal(SDImageCacheTypeDisk);
// Currently, thumbnail decoding does not write back to the original key's memory cache
// But this may change in the future once I change the API for `SDImageCacheProtocol`
expect([SDImageCache.sharedImageCache imageFromMemoryCacheForKey:fullSizeKey]).beNil();
expect([SDImageCache.sharedImageCache imageFromMemoryCacheForKey:thumbnailKey]).beNil();
// Check the full image should not be in memory cache (because we have only full data + thumbnail image)
// Check the thumbnail image should be in memory cache (because we have only full data + thumbnail image)
expect([SDImageCache.sharedImageCache imageFromMemoryCacheForKey:fullSizeKey].size).equal(CGSizeZero);
expect([SDImageCache.sharedImageCache imageFromDiskCacheForKey:fullSizeKey]).notTo.beNil();
expect([SDImageCache.sharedImageCache imageFromMemoryCacheForKey:thumbnailKey].size).equal(thumbnailSize);
expect([SDImageCache.sharedImageCache imageFromDiskCacheForKey:thumbnailKey]).beNil();
[SDImageCache.sharedImageCache removeImageFromDiskForKey:fullSizeKey];
[SDImageCache.sharedImageCache removeImageFromMemoryForKey:thumbnailKey];
[expectation fulfill];
}];