From 65e7e2a96394478de14054257fd5b27a8fc27013 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 6 Feb 2026 18:47:37 +0800 Subject: [PATCH 1/4] Fix the issue that thumbnail decoding will store the fulll image data into thumbnail key, which effect next time query from disk This is known issue in history which need a refactory. Currently have to workaround --- SDWebImage/Core/SDImageCache.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SDWebImage/Core/SDImageCache.m b/SDWebImage/Core/SDImageCache.m index 41d45a7d..5e5bb385 100644 --- a/SDWebImage/Core/SDImageCache.m +++ b/SDWebImage/Core/SDImageCache.m @@ -261,7 +261,10 @@ static NSString * _defaultDiskCacheDirectory; return; } NSData *data = imageData; - if (!data && [image respondsToSelector:@selector(animatedImageData)]) { + if (image.sd_isThumbnail && SDIsThumbnailKey(key)) { + // Currently we have no solid way to store thumbnail image's correct data + data = nil; + } else if (!data && [image respondsToSelector:@selector(animatedImageData)]) { // If image is custom animated image class, prefer its original animated data data = [((id)image) animatedImageData]; } From f93143e82014ccc8874a8414cc828cef44a601fc Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 6 Feb 2026 21:17:16 +0800 Subject: [PATCH 2/4] Fix the change issue that re-encode SDAnimatedImage thumbnail image will loss animation --- SDWebImage/Core/SDImageCache.m | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/SDWebImage/Core/SDImageCache.m b/SDWebImage/Core/SDImageCache.m index 5e5bb385..f917899a 100644 --- a/SDWebImage/Core/SDImageCache.m +++ b/SDWebImage/Core/SDImageCache.m @@ -9,6 +9,7 @@ #import "SDImageCache.h" #import "SDInternalMacros.h" #import "NSImage+Compatibility.h" +#import "UIImage+MultiFormat.h" #import "SDImageCodersManager.h" #import "SDImageCoderHelper.h" #import "SDAnimatedImage.h" @@ -282,11 +283,17 @@ static NSString * _defaultDiskCacheDirectory; format = [SDImageCoderHelper CGImageContainsAlpha:image.CGImage] ? SDImageFormatPNG : SDImageFormatJPEG; } } - id imageCoder = context[SDWebImageContextImageCoder]; - if (!imageCoder) { - imageCoder = [SDImageCodersManager sharedManager]; + NSData *encodedData; + if ([image respondsToSelector:@selector(animatedImageData)]) { + // This API will encode the animation for `SDAnimatedImage` with GIF format + encodedData = [image sd_imageDataAsFormat:format]; + } else { + id imageCoder = context[SDWebImageContextImageCoder]; + if (!imageCoder) { + imageCoder = [SDImageCodersManager sharedManager]; + } + encodedData = [imageCoder encodedDataWithImage:image format:format options:context[SDWebImageContextImageEncodeOptions]]; } - NSData *encodedData = [imageCoder encodedDataWithImage:image format:format options:context[SDWebImageContextImageEncodeOptions]]; dispatch_async(self.ioQueue, ^{ [self _storeImageDataToDisk:encodedData forKey:key]; [self _archivedDataWithImage:image forKey:key]; From 84c74d182b1b4b53b2c709ea384ac2bb8d5790f8 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 6 Feb 2026 21:22:42 +0800 Subject: [PATCH 3/4] Remove the un-related write back memory cache logic into manager --- SDWebImage/Core/SDImageCache.h | 4 +++ SDWebImage/Core/SDImageCache.m | 51 ----------------------------- SDWebImage/Core/SDWebImageManager.m | 12 +++++++ 3 files changed, 16 insertions(+), 51 deletions(-) diff --git a/SDWebImage/Core/SDImageCache.h b/SDWebImage/Core/SDImageCache.h index d576f43f..340f8923 100644 --- a/SDWebImage/Core/SDImageCache.h +++ b/SDWebImage/Core/SDImageCache.h @@ -312,6 +312,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { * @param doneBlock The completion block. Will not get called if the operation is cancelled * * @return a SDImageCacheToken instance containing the cache operation, will callback immediately when cancelled + * @note In history, when disk cache hit, the image will write back into memory cache, which is mostly unwanted behavior. From new version this behavior is removed. */ - (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; @@ -324,6 +325,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { * * @return a SDImageCacheToken instance containing the cache operation, will callback immediately when cancelled * @warning If you query with thumbnail cache key, you'd better not pass the thumbnail pixel size context, which is undefined behavior. + * @note In history, when disk cache hit, the image will write back into memory cache, which is mostly unwanted behavior. From new version this behavior is removed. */ - (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; @@ -337,6 +339,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { * * @return a SDImageCacheToken instance containing the cache operation, will callback immediately when cancellederation, will callback immediately when cancelled * @warning If you query with thumbnail cache key, you'd better not pass the thumbnail pixel size context, which is undefined behavior. + * @note In history, when disk cache hit, the image will write back into memory cache, which is mostly unwanted behavior. From new version this behavior is removed. */ - (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; @@ -351,6 +354,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { * * @return a SDImageCacheToken instance containing the cache operation, will callback immediately when cancelled * @warning If you query with thumbnail cache key, you'd better not pass the thumbnail pixel size context, which is undefined behavior. + * @note In history, when disk cache hit, the image will write back into memory cache, which is mostly unwanted behavior. From new version this behavior is removed. */ - (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; diff --git a/SDWebImage/Core/SDImageCache.m b/SDWebImage/Core/SDImageCache.m index f917899a..6e5b5127 100644 --- a/SDWebImage/Core/SDImageCache.m +++ b/SDWebImage/Core/SDImageCache.m @@ -17,7 +17,6 @@ #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) { @@ -448,16 +447,6 @@ static NSString * _defaultDiskCacheDirectory; } NSData *data = [self diskImageDataForKey:key]; UIImage *diskImage = [self diskImageForKey:key data:data options:options context:context]; - - BOOL shouldCacheToMemory = YES; - if (context[SDWebImageContextStoreCacheType]) { - SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue]; - shouldCacheToMemory = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory); - } - if (shouldCacheToMemory) { - // check if we need sync logic - [self _syncDiskToMemoryWithImage:diskImage forKey:key]; - } return diskImage; } @@ -537,42 +526,6 @@ 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 && !SDIsThumbnailKey(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; @@ -715,10 +668,6 @@ static NSString * _defaultDiskCacheDirectory; // decode image data only if in-memory cache missed if (!diskImage) { diskImage = [self diskImageForKey:key data:diskData options:options context:context]; - // check if we need sync logic - if (shouldCacheToMemory) { - [self _syncDiskToMemoryWithImage:diskImage forKey:key]; - } } } return diskImage; diff --git a/SDWebImage/Core/SDWebImageManager.m b/SDWebImage/Core/SDWebImageManager.m index 34255e1b..a1d380dd 100644 --- a/SDWebImage/Core/SDWebImageManager.m +++ b/SDWebImage/Core/SDWebImageManager.m @@ -324,6 +324,12 @@ static id _defaultImageLoader; [self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock]; return; } + } else { + // Write back the disk image into memory cache, with the correct key + if (cacheType == SDImageCacheTypeDisk) { + // Sync + [imageCache storeImage:cachedImage imageData:nil forKey:key cacheType:SDImageCacheTypeMemory completion:nil]; + } } // Continue download process [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock]; @@ -376,6 +382,12 @@ static id _defaultImageLoader; // Original image cache miss. Continue download process [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock]; return; + } else { + // Write back the disk image into memory cache, with the correct key + if (cacheType == SDImageCacheTypeDisk) { + // Sync + [imageCache storeImage:cachedImage imageData:nil forKey:key cacheType:SDImageCacheTypeMemory completion:nil]; + } } // Skip downloading and continue transform process, and ignore .refreshCached option for now From bedc005a764b6c4ecccbed6388f1231998feebaf Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 6 Feb 2026 21:46:29 +0800 Subject: [PATCH 4/4] Fix the unit testing for disk cache query --- SDWebImage/Core/SDImageCache.h | 1 + Tests/Tests/SDWebImageManagerTests.m | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/SDWebImage/Core/SDImageCache.h b/SDWebImage/Core/SDImageCache.h index 340f8923..4083c987 100644 --- a/SDWebImage/Core/SDImageCache.h +++ b/SDWebImage/Core/SDImageCache.h @@ -263,6 +263,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { * * @param imageData The image data to store * @param key The unique image cache key, usually it's image absolute URL + * @note In history, when disk cache hit, the image will write back into memory cache, which is mostly unwanted behavior. From new version this behavior is removed. */ - (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key; diff --git a/Tests/Tests/SDWebImageManagerTests.m b/Tests/Tests/SDWebImageManagerTests.m index 1f5abbb8..e85dba24 100644 --- a/Tests/Tests/SDWebImageManagerTests.m +++ b/Tests/Tests/SDWebImageManagerTests.m @@ -504,7 +504,8 @@ // 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); + // Store disk no longer store into memory +// expect([SDImageCache.sharedImageCache imageFromMemoryCacheForKey:thumbnailKey].size).equal(thumbnailSize); expect([SDImageCache.sharedImageCache imageFromDiskCacheForKey:thumbnailKey]).beNil(); [SDImageCache.sharedImageCache removeImageFromDiskForKey:fullSizeKey];