Merge pull request #3362 from dreampiggy/behavior_thumbnail_store_cache
Feature: Change thumbnail cache behavior as expected, share cache through different loading pipeline without extra download
This commit is contained in:
commit
d58a1006c2
|
@ -373,7 +373,11 @@ static NSString * _defaultDiskCacheDirectory;
|
|||
SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
|
||||
shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
|
||||
}
|
||||
if (diskImage && self.config.shouldCacheImagesInMemory && shouldCacheToMomery) {
|
||||
if (context[SDWebImageContextImageThumbnailPixelSize]) {
|
||||
// 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];
|
||||
}
|
||||
|
@ -581,6 +585,10 @@ static NSString * _defaultDiskCacheDirectory;
|
|||
SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
|
||||
shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
|
||||
}
|
||||
if (context[SDWebImageContextImageThumbnailPixelSize]) {
|
||||
// Query full size cache key which generate a thumbnail, should not write back to full size memory cache
|
||||
shouldCacheToMomery = NO;
|
||||
}
|
||||
// decode image data only if in-memory cache missed
|
||||
diskImage = [self diskImageForKey:key data:diskData options:options context:context];
|
||||
if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#import "SDWebImageCompat.h"
|
||||
#import "SDWebImageOperation.h"
|
||||
#import "SDWebImageDefine.h"
|
||||
#import "SDImageCoder.h"
|
||||
|
||||
/// Image Cache Type
|
||||
typedef NS_ENUM(NSInteger, SDImageCacheType) {
|
||||
|
@ -54,6 +55,12 @@ typedef void(^SDImageCacheContainsCompletionBlock)(SDImageCacheType containsCach
|
|||
*/
|
||||
FOUNDATION_EXPORT UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context);
|
||||
|
||||
/// Get the decode options from the loading context options and cache key. This is the built-in translate between the web loading part to the decoding part (which does not depens on).
|
||||
/// @param context The options arg from the input
|
||||
/// @param options The context arg from the input
|
||||
/// @param cacheKey The image cache key from the input. Should not be nil
|
||||
FOUNDATION_EXPORT SDImageCoderOptions * _Nonnull SDGetDecodeOptionsFromContext(SDWebImageContext * _Nullable context, SDWebImageOptions options, NSString * _Nonnull cacheKey);
|
||||
|
||||
/**
|
||||
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`).
|
||||
|
|
|
@ -13,8 +13,7 @@
|
|||
#import "UIImage+Metadata.h"
|
||||
#import "SDInternalMacros.h"
|
||||
|
||||
UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
|
||||
UIImage *image;
|
||||
SDImageCoderOptions * _Nonnull SDGetDecodeOptionsFromContext(SDWebImageContext * _Nullable context, SDWebImageOptions options, NSString * _Nonnull cacheKey) {
|
||||
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
|
||||
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
|
||||
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
|
||||
|
@ -38,6 +37,17 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
|
|||
mutableCoderOptions[SDImageCoderWebImageContext] = context;
|
||||
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
|
||||
|
||||
return coderOptions;
|
||||
}
|
||||
|
||||
UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
|
||||
NSCParameterAssert(imageData);
|
||||
NSCParameterAssert(cacheKey);
|
||||
UIImage *image;
|
||||
SDImageCoderOptions *coderOptions = SDGetDecodeOptionsFromContext(context, options, cacheKey);
|
||||
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
|
||||
CGFloat scale = [coderOptions[SDImageCoderDecodeScaleFactor] doubleValue];
|
||||
|
||||
// Grab the image coder
|
||||
id<SDImageCoder> imageCoder;
|
||||
if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) {
|
||||
|
@ -79,6 +89,8 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
|
|||
if (shouldDecode) {
|
||||
image = [SDImageCoderHelper decodedImageWithImage:image];
|
||||
}
|
||||
// assign the decode options, to let manager check whether to re-decode if needed
|
||||
image.sd_decodeOptions = coderOptions;
|
||||
}
|
||||
|
||||
return image;
|
||||
|
|
|
@ -311,11 +311,14 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
|||
// Which decode frames in time and reduce memory usage
|
||||
if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
|
||||
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
|
||||
NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
|
||||
imageRep.size = size;
|
||||
NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
|
||||
[animatedImage addRepresentation:imageRep];
|
||||
return animatedImage;
|
||||
if (imageRep) {
|
||||
NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
|
||||
imageRep.size = size;
|
||||
NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
|
||||
[animatedImage addRepresentation:imageRep];
|
||||
animatedImage.sd_imageFormat = self.class.imageFormat;
|
||||
return animatedImage;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#import "SDAnimatedImage.h"
|
||||
#import "UIImage+Metadata.h"
|
||||
#import "SDInternalMacros.h"
|
||||
#import "SDImageCacheDefine.h"
|
||||
#import "objc/runtime.h"
|
||||
|
||||
SDWebImageContextOption const SDWebImageContextLoaderCachedImage = @"loaderCachedImage";
|
||||
|
@ -41,28 +42,9 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
|
|||
} else {
|
||||
cacheKey = imageURL.absoluteString;
|
||||
}
|
||||
SDImageCoderOptions *coderOptions = SDGetDecodeOptionsFromContext(context, options, cacheKey);
|
||||
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
|
||||
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
|
||||
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
|
||||
NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
|
||||
NSValue *thumbnailSizeValue;
|
||||
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
|
||||
if (shouldScaleDown) {
|
||||
CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4;
|
||||
CGFloat dimension = ceil(sqrt(thumbnailPixels));
|
||||
thumbnailSizeValue = @(CGSizeMake(dimension, dimension));
|
||||
}
|
||||
if (context[SDWebImageContextImageThumbnailPixelSize]) {
|
||||
thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
|
||||
}
|
||||
|
||||
SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2];
|
||||
mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame);
|
||||
mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
|
||||
mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue;
|
||||
mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue;
|
||||
mutableCoderOptions[SDImageCoderWebImageContext] = context;
|
||||
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
|
||||
CGFloat scale = [coderOptions[SDImageCoderDecodeScaleFactor] doubleValue];
|
||||
|
||||
// Grab the image coder
|
||||
id<SDImageCoder> imageCoder;
|
||||
|
@ -106,6 +88,8 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
|
|||
if (shouldDecode) {
|
||||
image = [SDImageCoderHelper decodedImageWithImage:image];
|
||||
}
|
||||
// assign the decode options, to let manager check whether to re-decode if needed
|
||||
image.sd_decodeOptions = coderOptions;
|
||||
}
|
||||
|
||||
return image;
|
||||
|
@ -204,6 +188,8 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
|
|||
}
|
||||
// mark the image as progressive (completed one are not mark as progressive)
|
||||
image.sd_isIncremental = !finished;
|
||||
// assign the decode options, to let manager check whether to re-decode if needed
|
||||
image.sd_decodeOptions = coderOptions;
|
||||
}
|
||||
|
||||
return image;
|
||||
|
|
|
@ -112,6 +112,26 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
return key;
|
||||
}
|
||||
|
||||
- (nullable NSString *)originalCacheKeyForURL:(nullable NSURL *)url context:(nullable SDWebImageContext *)context {
|
||||
if (!url) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
NSString *key;
|
||||
// Cache Key Filter
|
||||
id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
|
||||
if (context[SDWebImageContextCacheKeyFilter]) {
|
||||
cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
|
||||
}
|
||||
if (cacheKeyFilter) {
|
||||
key = [cacheKeyFilter cacheKeyForURL:url];
|
||||
} else {
|
||||
key = url.absoluteString;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url context:(nullable SDWebImageContext *)context {
|
||||
if (!url) {
|
||||
return @"";
|
||||
|
@ -278,10 +298,14 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
[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;
|
||||
} else if (context[SDWebImageContextImageTransformer] && !cachedImage) {
|
||||
// Have a chance to query original cache instead of downloading
|
||||
[self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
|
||||
return;
|
||||
} else if (!cachedImage) {
|
||||
BOOL mayInOriginalCache = context[SDWebImageContextImageTransformer] || context[SDWebImageContextImageThumbnailPixelSize];
|
||||
// Have a chance to query original cache instead of downloading, then applying transform
|
||||
// Thumbnail decoding is done inside SDImageCache's decoding part, which does not need post processing for transform
|
||||
if (mayInOriginalCache) {
|
||||
[self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue download process
|
||||
|
@ -321,10 +345,8 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
// Check whether we should query original cache
|
||||
BOOL shouldQueryOriginalCache = (originalQueryCacheType != SDImageCacheTypeNone);
|
||||
if (shouldQueryOriginalCache) {
|
||||
// Disable transformer for original cache key generation
|
||||
SDWebImageMutableContext *tempContext = [context mutableCopy];
|
||||
tempContext[SDWebImageContextImageTransformer] = [NSNull null];
|
||||
NSString *key = [self cacheKeyForURL:url context:tempContext];
|
||||
// Get original cache key generation without transformer/thumbnail
|
||||
NSString *key = [self originalCacheKeyForURL:url context:context];
|
||||
@weakify(operation);
|
||||
operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:originalQueryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
|
||||
@strongify(operation);
|
||||
|
@ -333,20 +355,20 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
[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;
|
||||
} else if (context[SDWebImageContextImageTransformer] && !cachedImage) {
|
||||
} else if (!cachedImage) {
|
||||
// Original image cache miss. Continue download process
|
||||
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:originalQueryCacheType progress:progressBlock completed:completedBlock];
|
||||
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the store cache process instead of downloading, and ignore .refreshCached option for now
|
||||
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:cachedImage downloadedData:cachedData finished:YES progress:progressBlock completed:completedBlock];
|
||||
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:cachedImage downloadedData:cachedData cacheType:cacheType finished:YES completed:completedBlock];
|
||||
|
||||
[self safelyRemoveOperationFromRunning:operation];
|
||||
}];
|
||||
} else {
|
||||
// Continue download process
|
||||
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:originalQueryCacheType progress:progressBlock completed:completedBlock];
|
||||
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -420,7 +442,7 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
SD_UNLOCK(self->_failedURLsLock);
|
||||
}
|
||||
// Continue store cache process
|
||||
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
|
||||
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData cacheType:SDImageCacheTypeNone finished:finished completed:completedBlock];
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
|
@ -444,8 +466,8 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
context:(SDWebImageContext *)context
|
||||
downloadedImage:(nullable UIImage *)downloadedImage
|
||||
downloadedData:(nullable NSData *)downloadedData
|
||||
cacheType:(SDImageCacheType)cacheType
|
||||
finished:(BOOL)finished
|
||||
progress:(nullable SDImageLoaderProgressBlock)progressBlock
|
||||
completed:(nullable SDInternalCompletionBlock)completedBlock {
|
||||
// Grab the image cache to use, choose standalone original cache firstly
|
||||
id<SDImageCache> imageCache;
|
||||
|
@ -459,6 +481,7 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
imageCache = self.imageCache;
|
||||
}
|
||||
}
|
||||
BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
|
||||
// the target image store cache type
|
||||
SDImageCacheType storeCacheType = SDImageCacheTypeAll;
|
||||
if (context[SDWebImageContextStoreCacheType]) {
|
||||
|
@ -469,10 +492,6 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
if (context[SDWebImageContextOriginalStoreCacheType]) {
|
||||
originalStoreCacheType = [context[SDWebImageContextOriginalStoreCacheType] integerValue];
|
||||
}
|
||||
// Disable transformer for original cache key generation
|
||||
SDWebImageMutableContext *tempContext = [context mutableCopy];
|
||||
tempContext[SDWebImageContextImageTransformer] = [NSNull null];
|
||||
NSString *key = [self cacheKeyForURL:url context:tempContext];
|
||||
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
|
||||
if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
|
||||
transformer = nil;
|
||||
|
@ -482,31 +501,41 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
BOOL shouldTransformImage = downloadedImage && transformer;
|
||||
shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
|
||||
shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isVector || (options & SDWebImageTransformVectorImage));
|
||||
BOOL shouldCacheOriginal = downloadedImage && finished;
|
||||
BOOL shouldCacheOriginal = downloadedImage && finished && cacheType == SDImageCacheTypeNone;
|
||||
|
||||
// if available, store original image to cache
|
||||
if (shouldCacheOriginal) {
|
||||
// Get original cache key generation without transformer/thumbnail
|
||||
NSString *key = [self originalCacheKeyForURL:url context:context];
|
||||
// normally use the store cache type, but if target image is transformed, use original store cache type instead
|
||||
SDImageCacheType targetStoreCacheType = shouldTransformImage ? originalStoreCacheType : storeCacheType;
|
||||
if (cacheSerializer && (targetStoreCacheType == SDImageCacheTypeDisk || targetStoreCacheType == SDImageCacheTypeAll)) {
|
||||
UIImage *originalImage = downloadedImage;
|
||||
BOOL thumbnailed = context[SDWebImageContextImageThumbnailPixelSize] != nil;
|
||||
if (thumbnailed) {
|
||||
// Thumbnail decoding does not keep original image
|
||||
// Here we only store the original data to disk for original cache key
|
||||
// Store thumbnail image to memory for thumbnail cache key later in `storeTransformCacheProcess`
|
||||
originalImage = nil;
|
||||
}
|
||||
if (originalImage && cacheSerializer && (targetStoreCacheType == SDImageCacheTypeDisk || targetStoreCacheType == SDImageCacheTypeAll)) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
@autoreleasepool {
|
||||
NSData *cacheData = [cacheSerializer cacheDataWithImage:downloadedImage originalData:downloadedData imageURL:url];
|
||||
[self storeImage:downloadedImage imageData:cacheData forKey:key imageCache:imageCache cacheType:targetStoreCacheType options:options context:context completion:^{
|
||||
NSData *cacheData = [cacheSerializer cacheDataWithImage:originalImage originalData:downloadedData imageURL:url];
|
||||
[self storeImage:originalImage imageData:cacheData forKey:key imageCache:imageCache cacheType:targetStoreCacheType waitStoreCache:waitStoreCache completion:^{
|
||||
// Continue transform process
|
||||
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
|
||||
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:cacheType finished:finished completed:completedBlock];
|
||||
}];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
[self storeImage:downloadedImage imageData:downloadedData forKey:key imageCache:imageCache cacheType:targetStoreCacheType options:options context:context completion:^{
|
||||
[self storeImage:originalImage imageData:downloadedData forKey:key imageCache:imageCache cacheType:targetStoreCacheType waitStoreCache:waitStoreCache completion:^{
|
||||
// Continue transform process
|
||||
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
|
||||
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:cacheType finished:finished completed:completedBlock];
|
||||
}];
|
||||
}
|
||||
} else {
|
||||
// Continue transform process
|
||||
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
|
||||
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:cacheType finished:finished completed:completedBlock];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -517,39 +546,54 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
context:(SDWebImageContext *)context
|
||||
originalImage:(nullable UIImage *)originalImage
|
||||
originalData:(nullable NSData *)originalData
|
||||
cacheType:(SDImageCacheType)cacheType
|
||||
finished:(BOOL)finished
|
||||
progress:(nullable SDImageLoaderProgressBlock)progressBlock
|
||||
completed:(nullable SDInternalCompletionBlock)completedBlock {
|
||||
// Grab the image cache to use
|
||||
id<SDImageCache> imageCache;
|
||||
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
|
||||
imageCache = context[SDWebImageContextImageCache];
|
||||
} else {
|
||||
imageCache = self.imageCache;
|
||||
}
|
||||
// the target image store cache type
|
||||
SDImageCacheType storeCacheType = SDImageCacheTypeAll;
|
||||
if (context[SDWebImageContextStoreCacheType]) {
|
||||
storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
|
||||
}
|
||||
// transformed cache key
|
||||
NSString *key = [self cacheKeyForURL:url context:context];
|
||||
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
|
||||
if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
|
||||
transformer = nil;
|
||||
}
|
||||
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
|
||||
|
||||
// transformer check
|
||||
BOOL shouldTransformImage = originalImage && transformer;
|
||||
shouldTransformImage = shouldTransformImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
|
||||
shouldTransformImage = shouldTransformImage && (!originalImage.sd_isVector || (options & SDWebImageTransformVectorImage));
|
||||
// if available, store transformed image to cache
|
||||
|
||||
// thumbnail check
|
||||
// This exist when previous thumbnail pipeline callback into next full size pipeline, because we share the same URL download but need different image
|
||||
// Actually this is a hack, we attach the metadata into image object, which should design a better concept like `ImageInfo` and keep that around
|
||||
// Redecode need the full size data (progressive decoding or third-party loaders may callback nil data)
|
||||
BOOL shouldRedecodeFullImage = originalData && cacheType == SDImageCacheTypeNone;
|
||||
if (shouldRedecodeFullImage) {
|
||||
// If the retuened image decode options exist (some loaders impl does not use `SDImageLoaderDecode`) but does not match the options we provide, redecode
|
||||
SDImageCoderOptions *returnedDecodeOptions = originalImage.sd_decodeOptions;
|
||||
if (returnedDecodeOptions) {
|
||||
SDImageCoderOptions *decodeOptions = SDGetDecodeOptionsFromContext(context, options, url.absoluteString);
|
||||
shouldRedecodeFullImage = ![returnedDecodeOptions isEqualToDictionary:decodeOptions];
|
||||
} else {
|
||||
shouldRedecodeFullImage = NO;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldTransformImage) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
@autoreleasepool {
|
||||
UIImage *transformedImage = [transformer transformedImageWithImage:originalImage forKey:key];
|
||||
// transformed/thumbnailed cache key
|
||||
NSString *key = [self cacheKeyForURL:url context:context];
|
||||
// Case that transformer one thumbnail, which this time need full pixel image
|
||||
UIImage *fullSizeImage = originalImage;
|
||||
if (shouldRedecodeFullImage) {
|
||||
fullSizeImage = SDImageCacheDecodeImageData(originalData, key, options, context) ?: originalImage;
|
||||
}
|
||||
UIImage *transformedImage = [transformer transformedImageWithImage:fullSizeImage forKey:key];
|
||||
if (transformedImage && finished) {
|
||||
BOOL imageWasTransformed = ![transformedImage isEqual:originalImage];
|
||||
BOOL imageWasTransformed = ![transformedImage isEqual:fullSizeImage];
|
||||
NSData *cacheData;
|
||||
// pass nil if the image was transformed, so we can recalculate the data from the image
|
||||
if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
|
||||
|
@ -557,16 +601,71 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
} else {
|
||||
cacheData = (imageWasTransformed ? nil : originalData);
|
||||
}
|
||||
[self storeImage:transformedImage imageData:cacheData forKey:key imageCache:imageCache cacheType:storeCacheType options:options context:context completion:^{
|
||||
[self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
|
||||
}];
|
||||
// Continue store transform cache process
|
||||
[self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:transformedImage data:cacheData cacheType:cacheType transformed:imageWasTransformed finished:finished completed:completedBlock];
|
||||
} else {
|
||||
[self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
|
||||
// Continue store transform cache process
|
||||
[self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:fullSizeImage data:originalData cacheType:cacheType transformed:NO finished:finished completed:completedBlock];
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (shouldRedecodeFullImage) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
@autoreleasepool {
|
||||
// Re-decode because the returned image does not match current request pipeline's context
|
||||
UIImage *fullSizeImage = SDImageCacheDecodeImageData(originalData, url.absoluteString, options, context) ?: originalImage;
|
||||
// Continue store transform cache process
|
||||
[self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:fullSizeImage data:originalData cacheType:cacheType transformed:NO finished:finished completed:completedBlock];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
[self callCompletionBlockForOperation:operation completion:completedBlock image:originalImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
|
||||
// Continue store transform cache process
|
||||
[self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:originalImage data:originalData cacheType:cacheType transformed:NO finished:finished completed:completedBlock];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)callStoreTransformCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
|
||||
url:(nonnull NSURL *)url
|
||||
options:(SDWebImageOptions)options
|
||||
context:(SDWebImageContext *)context
|
||||
image:(nullable UIImage *)image
|
||||
data:(nullable NSData *)data
|
||||
cacheType:(SDImageCacheType)cacheType
|
||||
transformed:(BOOL)transformed
|
||||
finished:(BOOL)finished
|
||||
completed:(nullable SDInternalCompletionBlock)completedBlock {
|
||||
// Grab the image cache to use
|
||||
id<SDImageCache> imageCache;
|
||||
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
|
||||
imageCache = context[SDWebImageContextImageCache];
|
||||
} else {
|
||||
imageCache = self.imageCache;
|
||||
}
|
||||
BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
|
||||
// the target image store cache type
|
||||
SDImageCacheType storeCacheType = SDImageCacheTypeAll;
|
||||
if (context[SDWebImageContextStoreCacheType]) {
|
||||
storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
|
||||
}
|
||||
// Hack: SDImageCache's queryImage API handle the thumbnail context option (in `SDImageCacheDecodeImageData`)
|
||||
// but the storeImage does not handle the thumbnail context option
|
||||
// to keep exist SDImageCache's impl compatible, we introduce this helper
|
||||
NSData *cacheData = data;
|
||||
BOOL thumbnailed = context[SDWebImageContextImageThumbnailPixelSize] != nil;
|
||||
if (thumbnailed) {
|
||||
// Thumbnail decoding already stored original data before in `storeCacheProcess`
|
||||
// Here we only store the thumbnail image to memory for thumbnail cache key
|
||||
cacheData = nil;
|
||||
}
|
||||
BOOL shouldCache = transformed || thumbnailed;
|
||||
if (shouldCache) {
|
||||
// transformed/thumbnailed cache key
|
||||
NSString *key = [self cacheKeyForURL:url context:context];
|
||||
[self storeImage:image imageData:cacheData forKey:key imageCache:imageCache cacheType:storeCacheType waitStoreCache:waitStoreCache completion:^{
|
||||
[self callCompletionBlockForOperation:operation completion:completedBlock image:image data:data error:nil cacheType:cacheType finished:finished url:url];
|
||||
}];
|
||||
} else {
|
||||
[self callCompletionBlockForOperation:operation completion:completedBlock image:image data:data error:nil cacheType:cacheType finished:finished url:url];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -586,10 +685,8 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
forKey:(nullable NSString *)key
|
||||
imageCache:(nonnull id<SDImageCache>)imageCache
|
||||
cacheType:(SDImageCacheType)cacheType
|
||||
options:(SDWebImageOptions)options
|
||||
context:(nullable SDWebImageContext *)context
|
||||
waitStoreCache:(BOOL)waitStoreCache
|
||||
completion:(nullable SDWebImageNoParamsBlock)completion {
|
||||
BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
|
||||
// Check whether we should wait the store cache finished. If not, callback immediately
|
||||
[imageCache storeImage:image imageData:data forKey:key cacheType:cacheType completion:^{
|
||||
if (waitStoreCache) {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#import "SDWebImageCompat.h"
|
||||
#import "NSData+ImageContentType.h"
|
||||
#import "SDImageCoder.h"
|
||||
|
||||
/**
|
||||
UIImage category for image metadata, including animation, loop count, format, incremental, etc.
|
||||
|
@ -65,4 +66,12 @@
|
|||
*/
|
||||
@property (nonatomic, assign) BOOL sd_isIncremental;
|
||||
|
||||
/**
|
||||
A dictionary value contains the decode options when decoded from SDWebImage loading system (say, `SDImageCacheDecodeImageData/SDImageLoaderDecode[Progressive]ImageData`)
|
||||
It may not always available and only image decoding related options will be saved. (including [.decodeScaleFactor, .decodeThumbnailPixelSize, .decodePreserveAspectRatio, .decodeFirstFrameOnly])
|
||||
@note This is used to identify and check the image from downloader when multiple different request (which want different image thumbnail size, image class, etc) share the same URLOperation.
|
||||
@warning This API exist only because of current SDWebImageDownloader bad design which does not callback the context we call it. There will be refactory in future (API break) and you SHOULD NOT rely on this property at all.
|
||||
*/
|
||||
@property (nonatomic, copy) SDImageCoderOptions *sd_decodeOptions;
|
||||
|
||||
@end
|
||||
|
|
|
@ -186,4 +186,16 @@
|
|||
return value.boolValue;
|
||||
}
|
||||
|
||||
- (void)setSd_decodeOptions:(SDImageCoderOptions *)sd_decodeOptions {
|
||||
objc_setAssociatedObject(self, @selector(sd_decodeOptions), sd_decodeOptions, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
}
|
||||
|
||||
- (SDImageCoderOptions *)sd_decodeOptions {
|
||||
SDImageCoderOptions *value = objc_getAssociatedObject(self, @selector(sd_decodeOptions));
|
||||
if ([value isKindOfClass:NSDictionary.class]) {
|
||||
return value;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -18,6 +18,7 @@ void SDImageCopyAssociatedObject(UIImage * _Nullable source, UIImage * _Nullable
|
|||
}
|
||||
// Image Metadata
|
||||
target.sd_isIncremental = source.sd_isIncremental;
|
||||
target.sd_decodeOptions = source.sd_decodeOptions;
|
||||
target.sd_imageLoopCount = source.sd_imageLoopCount;
|
||||
target.sd_imageFormat = source.sd_imageFormat;
|
||||
// Force Decode
|
||||
|
|
|
@ -265,6 +265,7 @@
|
|||
NSUInteger defaultLimitBytes = SDImageCoderHelper.defaultScaleDownLimitBytes;
|
||||
SDImageCoderHelper.defaultScaleDownLimitBytes = 1000 * 1000 * 4; // Limit 1000x1000 pixel
|
||||
// From v5.5.0, the `SDWebImageScaleDownLargeImages` translate to `SDWebImageContextImageThumbnailPixelSize`, and works for progressive loading
|
||||
[SDImageCache.sharedImageCache removeImageFromDiskForKey:originalImageURL.absoluteString];
|
||||
[SDWebImageManager.sharedManager loadImageWithURL:originalImageURL options:SDWebImageScaleDownLargeImages | SDWebImageProgressiveLoad progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
|
||||
expect(image).notTo.beNil();
|
||||
expect(image.size).equal(CGSizeMake(1000, 1000));
|
||||
|
@ -417,6 +418,108 @@
|
|||
[self waitForExpectationsWithTimeout:kAsyncTestTimeout * 10 handler:nil];
|
||||
}
|
||||
|
||||
- (void)test17ThatThumbnailCacheQueryNotWriteToWrongKey {
|
||||
// 1. When query thumbnail decoding for SDImageCache, the thumbnailed image should not stored into full size key
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Thumbnail for cache should not store the wrong key"];
|
||||
|
||||
// 500x500
|
||||
CGSize fullSize = CGSizeMake(500, 500);
|
||||
SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:fullSize];
|
||||
UIImage *fullSizeImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
|
||||
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0);
|
||||
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
|
||||
CGContextFillRect(context, CGRectMake(0, 0, fullSize.width, fullSize.height));
|
||||
}];
|
||||
expect(fullSizeImage.size).equal(fullSize);
|
||||
|
||||
NSString *fullSizeKey = @"kTestRectangle";
|
||||
// Disk only
|
||||
[SDImageCache.sharedImageCache storeImageDataToDisk:fullSizeImage.sd_imageData forKey:fullSizeKey];
|
||||
|
||||
CGSize thumbnailSize = CGSizeMake(100, 100);
|
||||
NSString *thumbnailKey = SDThumbnailedKeyForKey(fullSizeKey, thumbnailSize, YES);
|
||||
// thumbnail size key miss, full size key hit
|
||||
[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();
|
||||
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithCommonTimeout];
|
||||
}
|
||||
|
||||
- (void)test18ThatThumbnailLoadingCanUseFullSizeCache {
|
||||
// 2. When using SDWebImageManager to load thumbnail image, it will prefers the full size image and thumbnail decoding on the fly, no network
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Thumbnail for loading should prefers full size cache when thumbnail cache miss, like Transformer behavior"];
|
||||
|
||||
// 500x500
|
||||
CGSize fullSize = CGSizeMake(500, 500);
|
||||
SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:fullSize];
|
||||
UIImage *fullSizeImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
|
||||
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0);
|
||||
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
|
||||
CGContextFillRect(context, CGRectMake(0, 0, fullSize.width, fullSize.height));
|
||||
}];
|
||||
expect(fullSizeImage.size).equal(fullSize);
|
||||
NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/500x500.png"];
|
||||
NSString *fullSizeKey = [SDWebImageManager.sharedManager cacheKeyForURL:url];
|
||||
[SDImageCache.sharedImageCache storeImageDataToDisk:fullSizeImage.sd_imageData forKey:fullSizeKey];
|
||||
|
||||
CGSize thumbnailSize = CGSizeMake(100, 100);
|
||||
NSString *thumbnailKey = SDThumbnailedKeyForKey(fullSizeKey, thumbnailSize, YES);
|
||||
[SDImageCache.sharedImageCache removeImageFromDiskForKey:thumbnailKey];
|
||||
// Load with thumbnail, should use full size cache instead to decode and scale down
|
||||
[SDWebImageManager.sharedManager loadImageWithURL:url options:0 context:@{SDWebImageContextImageThumbnailPixelSize : @(thumbnailSize)} progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
|
||||
expect(image.size).equal(thumbnailSize);
|
||||
expect(cacheType).equal(SDImageCacheTypeDisk);
|
||||
expect(finished).beTruthy();
|
||||
|
||||
// The thumbnail one should stored into memory and disk cache with thumbnail key as well
|
||||
expect([SDImageCache.sharedImageCache imageFromMemoryCacheForKey:thumbnailKey].size).equal(thumbnailSize);
|
||||
expect([SDImageCache.sharedImageCache imageFromDiskCacheForKey:thumbnailKey].size).equal(thumbnailSize);
|
||||
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithCommonTimeout];
|
||||
}
|
||||
|
||||
- (void)test19ThatDifferentThumbnailLoadShouldCallbackDifferentSize {
|
||||
// 3. Current SDWebImageDownloader use the **URL** as primiary key to bind operation, however, different loading pipeline may ask different image size for same URL, this design does not match
|
||||
// We use a hack logic to do a re-decode check when the callback image's decode options does not match the loading pipeline provided, it will re-decode the full data with global queue :)
|
||||
// Ugly unless we re-define the design of SDWebImageDownloader, maybe change that `addHandlersForProgress` with context options args as well. Different context options need different callback image
|
||||
|
||||
NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/501x501.png"];
|
||||
NSString *fullSizeKey = [SDWebImageManager.sharedManager cacheKeyForURL:url];
|
||||
[SDImageCache.sharedImageCache removeImageFromDiskForKey:fullSizeKey];
|
||||
for (int i = 490; i < 500; i++) {
|
||||
// 490x490, ..., 499x499
|
||||
CGSize thumbnailSize = CGSizeMake(i, i);
|
||||
NSString *thumbnailKey = SDThumbnailedKeyForKey(fullSizeKey, thumbnailSize, YES);
|
||||
[SDImageCache.sharedImageCache removeImageFromDiskForKey:thumbnailKey];
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Different thumbnail loading for same URL should callback different image size: (%dx%d)", i, i]];
|
||||
[SDImageCache.sharedImageCache removeImageFromDiskForKey:url.absoluteString];
|
||||
__block SDWebImageCombinedOperation *operation;
|
||||
operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:0 context:@{SDWebImageContextImageThumbnailPixelSize : @(thumbnailSize)} progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
|
||||
expect(image.size).equal(thumbnailSize);
|
||||
expect(cacheType).equal(SDImageCacheTypeNone);
|
||||
expect(finished).beTruthy();
|
||||
|
||||
NSURLRequest *request = ((SDWebImageDownloadToken *)operation.loaderOperation).request;
|
||||
NSLog(@"thumbnail image size: (%dx%d) loaded with the shared request: %p", i, i, request);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
}
|
||||
|
||||
[self waitForExpectationsWithTimeout:kAsyncTestTimeout * 5 handler:nil];
|
||||
}
|
||||
|
||||
- (NSString *)testJPEGPath {
|
||||
NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
|
||||
return [testBundle pathForResource:@"TestImage" ofType:@"jpg"];
|
||||
|
|
Loading…
Reference in New Issue