Use the new solution, store the original decode options, when multiple loading pipeline share the same URL callback, check it and re-decode if needed

This commit is contained in:
DreamPiggy 2022-06-25 22:13:24 +08:00
parent 911328c1b4
commit ecac56b726
7 changed files with 53 additions and 51 deletions

View File

@ -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`).

View File

@ -13,10 +13,7 @@
#import "UIImage+Metadata.h"
#import "SDInternalMacros.h"
UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
NSCParameterAssert(imageData);
NSCParameterAssert(cacheKey);
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);
@ -40,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)]) {
@ -81,8 +89,8 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image];
}
// mark the image as thumbnail, to let manager check whether to re-decode if needed
image.sd_isThumbnail = thumbnailSizeValue != nil;
// assign the decode options, to let manager check whether to re-decode if needed
image.sd_decodeOptions = coderOptions;
}
return image;

View File

@ -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,8 +88,8 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image];
}
// mark the image as thumbnail, to let manager check whether to re-decode if needed
image.sd_isThumbnail = thumbnailSizeValue != nil;
// assign the decode options, to let manager check whether to re-decode if needed
image.sd_decodeOptions = coderOptions;
}
return image;
@ -206,8 +188,8 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
}
// mark the image as progressive (completed one are not mark as progressive)
image.sd_isIncremental = !finished;
// mark the image as thumbnail, to let manager check whether to re-decode if needed
image.sd_isThumbnail = thumbnailSizeValue != nil;
// assign the decode options, to let manager check whether to re-decode if needed
image.sd_decodeOptions = coderOptions;
}
return image;

View File

@ -568,7 +568,11 @@ static id<SDImageLoader> _defaultImageLoader;
// 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
BOOL shouldDecodeFullImage = originalImage.sd_isThumbnail && context[SDWebImageContextImageThumbnailPixelSize] == nil;
// Should match impl in `SDImageCacheDecodeImageData/SDImageLoaderDecode[Progressive]ImageData`
SDImageCoderOptions *decodeOptions = SDGetDecodeOptionsFromContext(context, options, url.absoluteString);
SDImageCoderOptions *returnedDecodeOptions = originalImage.sd_decodeOptions;
// If the retuened image decode options exist (some loaders impl does not use `SDImageLoaderDecode`) but does not match the options we provide, redecode
BOOL shouldRedecodeFullImage = returnedDecodeOptions && ![returnedDecodeOptions isEqualToDictionary:decodeOptions];
if (shouldTransformImage) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@ -577,10 +581,8 @@ static id<SDImageLoader> _defaultImageLoader;
NSString *key = [self cacheKeyForURL:url context:context];
// Case that transformer one thumbnail, which this time need full pixel image
UIImage *fullSizeImage = originalImage;
if (shouldDecodeFullImage) {
SDWebImageMutableContext *tempContext = [context mutableCopy];
tempContext[SDWebImageContextImageThumbnailPixelSize] = nil;
fullSizeImage = SDImageCacheDecodeImageData(originalData, key, options, tempContext) ?: originalImage;
if (shouldRedecodeFullImage) {
fullSizeImage = SDImageCacheDecodeImageData(originalData, key, options, context) ?: originalImage;
}
UIImage *transformedImage = [transformer transformedImageWithImage:fullSizeImage forKey:key];
if (transformedImage && finished) {
@ -600,15 +602,11 @@ static id<SDImageLoader> _defaultImageLoader;
}
}
});
} else if (shouldDecodeFullImage) {
} else if (shouldRedecodeFullImage) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
// transformed/thumbnailed cache key
NSString *key = [self cacheKeyForURL:url context:context];
// disable thumbnail decoding
SDWebImageMutableContext *tempContext = [context mutableCopy];
tempContext[SDWebImageContextImageThumbnailPixelSize] = nil;
UIImage *fullSizeImage = SDImageCacheDecodeImageData(originalData, key, options, tempContext) ?: originalImage;
// 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];
}

View File

@ -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.
@ -66,8 +67,11 @@
@property (nonatomic, assign) BOOL sd_isIncremental;
/**
A bool value indicating whether the image is from thumbnail decoding and may be smaller than the full image data pixel size.
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, assign) BOOL sd_isThumbnail;
@property (nonatomic, copy) SDImageCoderOptions *sd_decodeOptions;
@end

View File

@ -186,13 +186,16 @@
return value.boolValue;
}
- (void)setSd_isThumbnail:(BOOL)sd_isThumbnail {
objc_setAssociatedObject(self, @selector(sd_isThumbnail), @(sd_isThumbnail), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- (void)setSd_decodeOptions:(SDImageCoderOptions *)sd_decodeOptions {
objc_setAssociatedObject(self, @selector(sd_decodeOptions), sd_decodeOptions, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (BOOL)sd_isThumbnail {
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_isThumbnail));
return value.boolValue;
- (SDImageCoderOptions *)sd_decodeOptions {
SDImageCoderOptions *value = objc_getAssociatedObject(self, @selector(sd_decodeOptions));
if ([value isKindOfClass:NSDictionary.class]) {
return value;
}
return nil;
}
@end

View File

@ -18,7 +18,7 @@ void SDImageCopyAssociatedObject(UIImage * _Nullable source, UIImage * _Nullable
}
// Image Metadata
target.sd_isIncremental = source.sd_isIncremental;
target.sd_isThumbnail = source.sd_isThumbnail;
target.sd_decodeOptions = source.sd_decodeOptions;
target.sd_imageLoopCount = source.sd_imageLoopCount;
target.sd_imageFormat = source.sd_imageFormat;
// Force Decode