From a84ade991c33f9e23b06d5f8aa3d41eb198e5ada Mon Sep 17 00:00:00 2001 From: zxiou Date: Sun, 26 Apr 2020 17:41:45 +0800 Subject: [PATCH] fix quary original cache for transformed image instead of downloadingq --- SDWebImage/Core/SDImageCacheDefine.h | 20 ++++++ SDWebImage/Core/SDImageCacheDefine.m | 5 ++ SDWebImage/Core/SDWebImageDefine.h | 7 ++ SDWebImage/Core/SDWebImageDefine.m | 1 + SDWebImage/Core/SDWebImageManager.m | 95 ++++++++++++++++++++++++++-- 5 files changed, 124 insertions(+), 4 deletions(-) diff --git a/SDWebImage/Core/SDImageCacheDefine.h b/SDWebImage/Core/SDImageCacheDefine.h index e2449bfd..2faa2c6e 100644 --- a/SDWebImage/Core/SDImageCacheDefine.h +++ b/SDWebImage/Core/SDImageCacheDefine.h @@ -42,6 +42,24 @@ typedef NSString * _Nullable (^SDImageCacheAdditionalCachePathBlock)(NSString * typedef void(^SDImageCacheQueryCompletionBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType); typedef void(^SDImageCacheContainsCompletionBlock)(SDImageCacheType containsCacheType); +#pragma mark - Context Options + +/** + A `UIImage` instance from `SDWebImageCache` when miss normal cache and you specify `SDWebImageContextOriginalQueryCacheType` + and image cache hit. This can be used directly instead of downloading. + @note If you don't implement `SDWebImageContextOriginalQueryCacheType` support, you do not need to care abot this context option. + */ +FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalCachedImage; + +/** + A `NSData` instance from `SDWebImageCache` when miss normal cache and you specify `SDWebImageContextOriginalQueryCacheType` + and image cache hit. This can be used directly instead of downloading. + @note If you don't implement `SDWebImageContextOriginalQueryCacheType` support, you do not need to care abot this context option. + */ +FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalCachedImageData; + +#pragma mark - Helper method + /** This is the built-in decoding process for image query from cache. @note If you want to implement your custom loader with `queryImageForKey:options:context:completion:` API, but also want to keep compatible with SDWebImage's behavior, you'd better use this to produce image. @@ -54,6 +72,8 @@ typedef void(^SDImageCacheContainsCompletionBlock)(SDImageCacheType containsCach */ FOUNDATION_EXPORT UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context); +#pragma mark - SDImageCache + /** This is the image cache protocol to provide custom image cache for `SDWebImageManager`. Though the best practice to custom image cache, is to write your own class which conform `SDMemoryCache` or `SDDiskCache` protocol for `SDImageCache` class (See more on `SDImageCacheConfig.memoryCacheClass & SDImageCacheConfig.diskCacheClass`). diff --git a/SDWebImage/Core/SDImageCacheDefine.m b/SDWebImage/Core/SDImageCacheDefine.m index 19db161a..1d77818f 100644 --- a/SDWebImage/Core/SDImageCacheDefine.m +++ b/SDWebImage/Core/SDImageCacheDefine.m @@ -83,3 +83,8 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS return image; } + +#pragma mark - Context option + +SDWebImageContextOption const SDWebImageContextOriginalCachedImage = @"originalCachedImage"; +SDWebImageContextOption const SDWebImageContextOriginalCachedImageData = @"originalCachedImageData"; diff --git a/SDWebImage/Core/SDWebImageDefine.h b/SDWebImage/Core/SDWebImageDefine.h index 96f71d68..40808047 100644 --- a/SDWebImage/Core/SDWebImageDefine.h +++ b/SDWebImage/Core/SDWebImageDefine.h @@ -272,6 +272,13 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextQueryC */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextStoreCacheType; + +/** + The same behavior like `SDWebImageContextQueryCacheType`, but control the query cache type for the original image when you use image transformer feature. This allows the detail control of cache query for these two images. For example, if you want to query the transformed image from both memory/disk cache, query the original image from disk cache only, use `[.queryCacheType : .all, .originalQueryCacheType : .disk]` + If not provide or the value is invalid, we will use `SDImageCacheTypeNone`, which does not query the original image from cache. (NSNumber) + */ +FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalQueryCacheType; + /** The same behavior like `SDWebImageContextStoreCacheType`, but control the store cache type for the original image when you use image transformer feature. This allows the detail control of cache storage for these two images. For example, if you want to store the transformed image into both memory/disk cache, store the original image into disk cache only, use `[.storeCacheType : .all, .originalStoreCacheType : .disk]` If not provide or the value is invalid, we will use `SDImageCacheTypeNone`, which does not store the original image into cache. (NSNumber) diff --git a/SDWebImage/Core/SDWebImageDefine.m b/SDWebImage/Core/SDWebImageDefine.m index 2809af69..4c58061d 100644 --- a/SDWebImage/Core/SDWebImageDefine.m +++ b/SDWebImage/Core/SDWebImageDefine.m @@ -129,6 +129,7 @@ SDWebImageContextOption const SDWebImageContextImagePreserveAspectRatio = @"imag SDWebImageContextOption const SDWebImageContextImageThumbnailPixelSize = @"imageThumbnailPixelSize"; SDWebImageContextOption const SDWebImageContextQueryCacheType = @"queryCacheType"; SDWebImageContextOption const SDWebImageContextStoreCacheType = @"storeCacheType"; +SDWebImageContextOption const SDWebImageContextOriginalQueryCacheType = @"originalQueryCacheType"; SDWebImageContextOption const SDWebImageContextOriginalStoreCacheType = @"originalStoreCacheType"; SDWebImageContextOption const SDWebImageContextAnimatedImageClass = @"animatedImageClass"; SDWebImageContextOption const SDWebImageContextDownloadRequestModifier = @"downloadRequestModifier"; diff --git a/SDWebImage/Core/SDWebImageManager.m b/SDWebImage/Core/SDWebImageManager.m index 2adf2b64..d1982ae3 100644 --- a/SDWebImage/Core/SDWebImageManager.m +++ b/SDWebImage/Core/SDWebImageManager.m @@ -228,7 +228,7 @@ static id _defaultImageLoader; #pragma mark - Private -// Query cache process +// Query normal cache process - (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options @@ -242,13 +242,15 @@ static id _defaultImageLoader; } else { imageCache = self.imageCache; } - // Check whether we should query cache - BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly); + // Get the query cache type SDImageCacheType queryCacheType = SDImageCacheTypeAll; if (context[SDWebImageContextQueryCacheType]) { queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue]; } + + // Check whether we should query cache + BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly); if (shouldQueryCache) { NSString *key = [self cacheKeyForURL:url context:context]; @weakify(operation); @@ -260,6 +262,12 @@ static id _defaultImageLoader; [self safelyRemoveOperationFromRunning:operation]; return; } + else if (!cachedImage) { + // Miss normal cache, we have a chance to quary original image from cache instead of downloading + [self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock]; + return ; + } + // Continue download process [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock]; }]; @@ -269,6 +277,77 @@ static id _defaultImageLoader; } } +// Query original cache process +- (void)callOriginalCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation + url:(nonnull NSURL *)url + options:(SDWebImageOptions)options + context:(nullable SDWebImageContext *)context + progress:(nullable SDImageLoaderProgressBlock)progressBlock + completed:(nullable SDInternalCompletionBlock)completedBlock { + // Grab the image cache to use + id imageCache; + if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { + imageCache = context[SDWebImageContextImageCache]; + } else { + imageCache = self.imageCache; + } + + // Get the original query cache type + SDImageCacheType originalQueryCacheType = SDImageCacheTypeNone; + if (context[SDWebImageContextOriginalQueryCacheType]) { + originalQueryCacheType = [context[SDWebImageContextOriginalQueryCacheType] integerValue]; + } + + // Check whether we should query original cache + BOOL shouldQueryOriginalCache = context[SDWebImageContextImageTransformer] && (originalQueryCacheType != SDImageCacheTypeNone); + if (shouldQueryOriginalCache) { + // Change originContext to mutable + SDWebImageMutableContext * __block originContext; + if (context) { + originContext = [context mutableCopy]; + } else { + originContext = [NSMutableDictionary dictionary]; + } + + // disable transformer for cache key generation + id transformer = originContext[SDWebImageContextImageTransformer]; + originContext[SDWebImageContextImageTransformer] = [NSNull null]; + + NSString *key = [self cacheKeyForURL:url context:originContext]; + @weakify(operation); + operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:originalQueryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) { + @strongify(operation); + if (!operation || operation.isCancelled) { + // Image combined operation cancelled by user + [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url]; + [self safelyRemoveOperationFromRunning:operation]; + return; + } + + // Add original transformer + if (transformer) { + originContext[SDWebImageContextImageTransformer] = transformer; + } + + // Pass the original cached image and date to the next process + if (cachedImage) { + originContext[SDWebImageContextOriginalCachedImage] = cachedImage; + } + + if (cachedData) { + originContext[SDWebImageContextOriginalCachedImageData] = cachedData; + } + + // Continue download process + [self callDownloadProcessForOperation:operation url:url options:options context:[originContext copy] cachedImage:nil cachedData:nil cacheType:originalQueryCacheType progress:progressBlock completed:completedBlock]; + }]; + } + else { + // Continue download process + [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:originalQueryCacheType progress:progressBlock completed:completedBlock]; + } +} + // Download process - (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url @@ -286,9 +365,13 @@ static id _defaultImageLoader; } else { imageLoader = self.imageLoader; } + + // Get original chched image + UIImage *originalCachedImage = context[SDWebImageContextOriginalCachedImage]; + // Check whether we should download image from network BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly); - shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached); + shouldDownload &= ((!cachedImage && !originalCachedImage) || options & SDWebImageRefreshCached); shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]); shouldDownload &= [imageLoader canRequestImageForURL:url]; if (shouldDownload) { @@ -344,6 +427,10 @@ static id _defaultImageLoader; } else if (cachedImage) { [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; [self safelyRemoveOperationFromRunning:operation]; + } else if (originalCachedImage) { + // Still use the store cache process, but only instead of downloading + [self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:originalCachedImage downloadedData:context[SDWebImageContextOriginalCachedImageData] finished:YES progress:progressBlock completed:completedBlock]; + [self safelyRemoveOperationFromRunning:operation]; } else { // Image not in cache and download disallowed by delegate [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];