Merge pull request #3423 from dreampiggy/feature/refactor_thumbnail_hack_with_decode_options
Refactor the hack for multiple thumbnail image request at the same time
This commit is contained in:
commit
5ee6ac2476
|
@ -409,7 +409,16 @@ static NSString * _defaultDiskCacheDirectory;
|
|||
SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
|
||||
shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
|
||||
}
|
||||
if (context[SDWebImageContextImageThumbnailPixelSize]) {
|
||||
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;
|
||||
}
|
||||
|
@ -626,7 +635,16 @@ static NSString * _defaultDiskCacheDirectory;
|
|||
SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
|
||||
shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
|
||||
}
|
||||
if (context[SDWebImageContextImageThumbnailPixelSize]) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -55,12 +55,18 @@ 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
|
||||
/// 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 depends on).
|
||||
/// @param context The context arg from the input
|
||||
/// @param options The options 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);
|
||||
|
||||
/// Set the decode options to the loading context options. This is the built-in translate between the web loading part from the decoding part (which does not depends on).
|
||||
/// @param mutableContext The context arg to override
|
||||
/// @param mutableOptions The options arg to override
|
||||
/// @param decodeOptions The image decoding options
|
||||
FOUNDATION_EXPORT void SDSetDecodeOptionsToContext(SDWebImageMutableContext * _Nonnull mutableContext, SDWebImageOptions * _Nonnull mutableOptions, SDImageCoderOptions * _Nonnull decodeOptions);
|
||||
|
||||
/**
|
||||
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,6 +13,8 @@
|
|||
#import "UIImage+Metadata.h"
|
||||
#import "SDInternalMacros.h"
|
||||
|
||||
#import <CoreServices/CoreServices.h>
|
||||
|
||||
SDImageCoderOptions * _Nonnull SDGetDecodeOptionsFromContext(SDWebImageContext * _Nullable context, SDWebImageOptions options, NSString * _Nonnull cacheKey) {
|
||||
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
|
||||
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
|
||||
|
@ -29,7 +31,11 @@ SDImageCoderOptions * _Nonnull SDGetDecodeOptionsFromContext(SDWebImageContext *
|
|||
thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
|
||||
}
|
||||
NSString *typeIdentifierHint = context[SDWebImageContextImageTypeIdentifierHint];
|
||||
NSString *fileExtensionHint = cacheKey.pathExtension; // without dot
|
||||
NSString *fileExtensionHint;
|
||||
if (!typeIdentifierHint) {
|
||||
// UTI has high priority
|
||||
fileExtensionHint = cacheKey.pathExtension; // without dot
|
||||
}
|
||||
|
||||
// First check if user provided decode options
|
||||
SDImageCoderMutableOptions *mutableCoderOptions;
|
||||
|
@ -50,6 +56,27 @@ SDImageCoderOptions * _Nonnull SDGetDecodeOptionsFromContext(SDWebImageContext *
|
|||
return [mutableCoderOptions copy];
|
||||
}
|
||||
|
||||
void SDSetDecodeOptionsToContext(SDWebImageMutableContext * _Nonnull mutableContext, SDWebImageOptions * _Nonnull mutableOptions, SDImageCoderOptions * _Nonnull decodeOptions) {
|
||||
if ([decodeOptions[SDImageCoderDecodeFirstFrameOnly] boolValue]) {
|
||||
*mutableOptions |= SDWebImageDecodeFirstFrameOnly;
|
||||
} else {
|
||||
*mutableOptions &= ~SDWebImageDecodeFirstFrameOnly;
|
||||
}
|
||||
|
||||
mutableContext[SDWebImageContextImageScaleFactor] = decodeOptions[SDImageCoderDecodeScaleFactor];
|
||||
mutableContext[SDWebImageContextImagePreserveAspectRatio] = decodeOptions[SDImageCoderDecodePreserveAspectRatio];
|
||||
mutableContext[SDWebImageContextImageThumbnailPixelSize] = decodeOptions[SDImageCoderDecodeThumbnailPixelSize];
|
||||
|
||||
NSString *typeIdentifierHint = decodeOptions[SDImageCoderDecodeTypeIdentifierHint];
|
||||
if (!typeIdentifierHint) {
|
||||
NSString *fileExtensionHint = decodeOptions[SDImageCoderDecodeFileExtensionHint];
|
||||
if (fileExtensionHint) {
|
||||
typeIdentifierHint = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtensionHint, NULL);
|
||||
}
|
||||
}
|
||||
mutableContext[SDWebImageContextImageTypeIdentifierHint] = typeIdentifierHint;
|
||||
}
|
||||
|
||||
UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
|
||||
NSCParameterAssert(imageData);
|
||||
NSCParameterAssert(cacheKey);
|
||||
|
|
|
@ -19,7 +19,7 @@ typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull i
|
|||
|
||||
/// Provide the image data associated to the image and store to disk cache
|
||||
/// @param image The loaded image
|
||||
/// @param data The original loaded image data
|
||||
/// @param data The original loaded image data. May be nil when image is transformed (UIImage.sd_isTransformed = YES)
|
||||
/// @param imageURL The image URL
|
||||
- (nullable NSData *)cacheDataWithImage:(nonnull UIImage *)image originalData:(nullable NSData *)data imageURL:(nullable NSURL *)imageURL;
|
||||
|
||||
|
|
|
@ -241,6 +241,7 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageC
|
|||
|
||||
/**
|
||||
A id<SDImageTransformer> instance which conforms `SDImageTransformer` protocol. It's used for image transform after the image load finished and store the transformed image to cache. If you provide one, it will ignore the `transformer` in manager and use provided one instead. If you pass NSNull, the transformer feature will be disabled. (id<SDImageTransformer>)
|
||||
@note When this value is used, we will trigger image transform after downloaded, and the callback's data **will be nil** (because this time the data saved to disk does not match the image return to you. If you need full size data, query the cache with full size url key)
|
||||
*/
|
||||
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageTransformer;
|
||||
|
||||
|
@ -269,6 +270,7 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageP
|
|||
A CGSize raw value indicating whether or not to generate the thumbnail images (or bitmap images from vector format). When this value is provided, the decoder will generate a thumbnail image which pixel size is smaller than or equal to (depends the `.imagePreserveAspectRatio`) the value size.
|
||||
@note When you pass `.preserveAspectRatio == NO`, the thumbnail image is stretched to match each dimension. When `.preserveAspectRatio == YES`, the thumbnail image's width is limited to pixel size's width, the thumbnail image's height is limited to pixel size's height. For common cases, you can just pass a square size to limit both.
|
||||
Defaults to CGSizeZero, which means no thumbnail generation at all. (NSValue)
|
||||
@note When this value is used, we will trigger thumbnail decoding for url, and the callback's data **will be nil** (because this time the data saved to disk does not match the image return to you. If you need full size data, query the cache with full size url key)
|
||||
*/
|
||||
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageThumbnailPixelSize;
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#import "SDWebImageDownloaderConfig.h"
|
||||
#import "SDWebImageDownloaderOperation.h"
|
||||
#import "SDWebImageError.h"
|
||||
#import "SDWebImageCacheKeyFilter.h"
|
||||
#import "SDImageCacheDefine.h"
|
||||
#import "SDInternalMacros.h"
|
||||
|
||||
NSNotificationName const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
|
||||
|
@ -206,6 +208,15 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
|
|||
|
||||
SD_LOCK(_operationsLock);
|
||||
id downloadOperationCancelToken;
|
||||
// When different thumbnail size download with same url, we need to make sure each callback called with desired size
|
||||
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
|
||||
NSString *cacheKey;
|
||||
if (cacheKeyFilter) {
|
||||
cacheKey = [cacheKeyFilter cacheKeyForURL:url];
|
||||
} else {
|
||||
cacheKey = url.absoluteString;
|
||||
}
|
||||
SDImageCoderOptions *decodeOptions = SDGetDecodeOptionsFromContext(context, [self.class imageOptionsFromDownloaderOptions:options], cacheKey);
|
||||
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
|
||||
// There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.
|
||||
if (!operation || operation.isFinished || operation.isCancelled) {
|
||||
|
@ -228,9 +239,9 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
|
|||
[self.URLOperations removeObjectForKey:url];
|
||||
SD_UNLOCK(self->_operationsLock);
|
||||
};
|
||||
self.URLOperations[url] = operation;
|
||||
[self.URLOperations setObject:operation forKey:url];
|
||||
// Add the handlers before submitting to operation queue, avoid the race condition that operation finished before setting handlers.
|
||||
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
|
||||
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock decodeOptions:decodeOptions];
|
||||
// Add operation to operation queue only after all configuration done according to Apple's doc.
|
||||
// `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
|
||||
[self.downloadQueue addOperation:operation];
|
||||
|
@ -238,7 +249,7 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
|
|||
// When we reuse the download operation to attach more callbacks, there may be thread safe issue because the getter of callbacks may in another queue (decoding queue or delegate queue)
|
||||
// So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
|
||||
@synchronized (operation) {
|
||||
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
|
||||
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock decodeOptions:decodeOptions];
|
||||
}
|
||||
if (!operation.isExecuting) {
|
||||
if (options & SDWebImageDownloaderHighPriority) {
|
||||
|
@ -260,6 +271,18 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
|
|||
return token;
|
||||
}
|
||||
|
||||
#pragma mark Helper methods
|
||||
+ (SDWebImageOptions)imageOptionsFromDownloaderOptions:(SDWebImageDownloaderOptions)downloadOptions {
|
||||
SDWebImageOptions options = 0;
|
||||
if (downloadOptions & SDWebImageDownloaderScaleDownLargeImages) options |= SDWebImageScaleDownLargeImages;
|
||||
if (downloadOptions & SDWebImageDownloaderDecodeFirstFrameOnly) options |= SDWebImageDecodeFirstFrameOnly;
|
||||
if (downloadOptions & SDWebImageDownloaderPreloadAllFrames) options |= SDWebImagePreloadAllFrames;
|
||||
if (downloadOptions & SDWebImageDownloaderAvoidDecodeImage) options |= SDWebImageAvoidDecodeImage;
|
||||
if (downloadOptions & SDWebImageDownloaderMatchAnimatedImageClass) options |= SDWebImageMatchAnimatedImageClass;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
|
||||
options:(SDWebImageDownloaderOptions)options
|
||||
context:(nullable SDWebImageContext *)context {
|
||||
|
|
|
@ -29,6 +29,10 @@
|
|||
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
||||
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
|
||||
|
||||
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
||||
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock
|
||||
decodeOptions:(nullable SDImageCoderOptions *)decodeOptions;
|
||||
|
||||
- (BOOL)cancel:(nullable id)token;
|
||||
|
||||
@property (strong, nonatomic, readonly, nullable) NSURLRequest *request;
|
||||
|
@ -160,6 +164,21 @@
|
|||
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
||||
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
|
||||
|
||||
/**
|
||||
* Adds handlers for progress and completion, and optional decode options (which need another image other than the initial one). Returns a token that can be passed to -cancel: to cancel this set of
|
||||
* callbacks.
|
||||
*
|
||||
* @param progressBlock the block executed when a new chunk of data arrives.
|
||||
* @note the progress block is executed on a background queue
|
||||
* @param completedBlock the block executed when the download is done.
|
||||
* @note the completed block is executed on the main queue for success. If errors are found, there is a chance the block will be executed on a background queue
|
||||
* @param decodeOptions The optional decode options, used when in thumbnail decoding for current completion block callback. For example, request <url1, {thumbnail: 100x100}> and then <url1, {thumbnail: 200x200}>, we may callback these two completion block with different size.
|
||||
* @return the token to use to cancel this set of handlers
|
||||
*/
|
||||
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
||||
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock
|
||||
decodeOptions:(nullable SDImageCoderOptions *)decodeOptions;
|
||||
|
||||
/**
|
||||
* Cancels a set of callbacks. Once all callbacks are canceled, the operation is cancelled.
|
||||
*
|
||||
|
|
|
@ -11,15 +11,40 @@
|
|||
#import "SDInternalMacros.h"
|
||||
#import "SDWebImageDownloaderResponseModifier.h"
|
||||
#import "SDWebImageDownloaderDecryptor.h"
|
||||
#import "SDImageCacheDefine.h"
|
||||
|
||||
static NSString *const kProgressCallbackKey = @"progress";
|
||||
static NSString *const kCompletedCallbackKey = @"completed";
|
||||
// A handler to represent individual request
|
||||
@interface SDWebImageDownloaderOperationToken : NSObject
|
||||
|
||||
typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
|
||||
@property (nonatomic, copy, nullable) SDWebImageDownloaderCompletedBlock completedBlock;
|
||||
@property (nonatomic, copy, nullable) SDWebImageDownloaderProgressBlock progressBlock;
|
||||
@property (nonatomic, copy, nullable) SDImageCoderOptions *decodeOptions;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SDWebImageDownloaderOperationToken
|
||||
|
||||
- (BOOL)isEqual:(id)other {
|
||||
if (nil == other) {
|
||||
return NO;
|
||||
}
|
||||
if (self == other) {
|
||||
return YES;
|
||||
}
|
||||
if (![other isKindOfClass:[self class]]) {
|
||||
return NO;
|
||||
}
|
||||
SDWebImageDownloaderOperationToken *object = (SDWebImageDownloaderOperationToken *)other;
|
||||
// warn: only compare decodeOptions, ignore pointer, use `removeObjectIdenticalTo`
|
||||
BOOL result = [self.decodeOptions isEqualToDictionary:object.decodeOptions];
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface SDWebImageDownloaderOperation ()
|
||||
|
||||
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;
|
||||
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageDownloaderOperationToken *> *callbackTokens;
|
||||
|
||||
@property (assign, nonatomic, readwrite) SDWebImageDownloaderOptions options;
|
||||
@property (copy, nonatomic, readwrite, nullable) SDWebImageContext *context;
|
||||
|
@ -48,6 +73,8 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
|
|||
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
|
||||
|
||||
@property (strong, nonatomic, nonnull) NSOperationQueue *coderQueue; // the serial operation queue to do image decoding
|
||||
|
||||
@property (strong, nonatomic, nonnull) NSMapTable<SDImageCoderOptions *, UIImage *> *imageMap; // each variant of image is weak-referenced to avoid too many re-decode during downloading
|
||||
#if SD_UIKIT
|
||||
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
|
||||
#endif
|
||||
|
@ -75,7 +102,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
|
|||
_request = [request copy];
|
||||
_options = options;
|
||||
_context = [context copy];
|
||||
_callbackBlocks = [NSMutableArray new];
|
||||
_callbackTokens = [NSMutableArray new];
|
||||
_responseModifier = context[SDWebImageContextDownloadResponseModifier];
|
||||
_decryptor = context[SDWebImageContextDownloadDecryptor];
|
||||
_executing = NO;
|
||||
|
@ -84,6 +111,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
|
|||
_unownedSession = session;
|
||||
_coderQueue = [NSOperationQueue new];
|
||||
_coderQueue.maxConcurrentOperationCount = 1;
|
||||
_imageMap = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:1];
|
||||
#if SD_UIKIT
|
||||
_backgroundTaskId = UIBackgroundTaskInvalid;
|
||||
#endif
|
||||
|
@ -93,33 +121,31 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
|
|||
|
||||
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
||||
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
|
||||
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
|
||||
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
|
||||
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
|
||||
@synchronized (self) {
|
||||
[self.callbackBlocks addObject:callbacks];
|
||||
}
|
||||
return callbacks;
|
||||
return [self addHandlersForProgress:progressBlock completed:completedBlock decodeOptions:nil];
|
||||
}
|
||||
|
||||
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
|
||||
NSMutableArray<id> *callbacks;
|
||||
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
||||
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock
|
||||
decodeOptions:(nullable SDImageCoderOptions *)decodeOptions {
|
||||
if (!completedBlock && !progressBlock && !decodeOptions) return nil;
|
||||
SDWebImageDownloaderOperationToken *token = [SDWebImageDownloaderOperationToken new];
|
||||
token.completedBlock = completedBlock;
|
||||
token.progressBlock = progressBlock;
|
||||
token.decodeOptions = decodeOptions;
|
||||
@synchronized (self) {
|
||||
callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
|
||||
[self.callbackTokens addObject:token];
|
||||
}
|
||||
// We need to remove [NSNull null] because there might not always be a progress block for each callback
|
||||
[callbacks removeObjectIdenticalTo:[NSNull null]];
|
||||
return [callbacks copy]; // strip mutability here
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
- (BOOL)cancel:(nullable id)token {
|
||||
if (!token) return NO;
|
||||
if (![token isKindOfClass:SDWebImageDownloaderOperationToken.class]) return NO;
|
||||
|
||||
BOOL shouldCancel = NO;
|
||||
@synchronized (self) {
|
||||
NSMutableArray *tempCallbackBlocks = [self.callbackBlocks mutableCopy];
|
||||
[tempCallbackBlocks removeObjectIdenticalTo:token];
|
||||
if (tempCallbackBlocks.count == 0) {
|
||||
NSArray *tokens = self.callbackTokens;
|
||||
if (tokens.count == 1 && [tokens indexOfObjectIdenticalTo:token] != NSNotFound) {
|
||||
shouldCancel = YES;
|
||||
}
|
||||
}
|
||||
|
@ -129,9 +155,9 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
|
|||
} else {
|
||||
// Only callback this token's completion block
|
||||
@synchronized (self) {
|
||||
[self.callbackBlocks removeObjectIdenticalTo:token];
|
||||
[self.callbackTokens removeObjectIdenticalTo:token];
|
||||
}
|
||||
SDWebImageDownloaderCompletedBlock completedBlock = [token valueForKey:kCompletedCallbackKey];
|
||||
SDWebImageDownloaderCompletedBlock completedBlock = ((SDWebImageDownloaderOperationToken *)token).completedBlock;
|
||||
dispatch_main_async_safe(^{
|
||||
if (completedBlock) {
|
||||
completedBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}], YES);
|
||||
|
@ -218,8 +244,11 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
|
|||
self.coderQueue.qualityOfService = NSQualityOfServiceDefault;
|
||||
}
|
||||
[self.dataTask resume];
|
||||
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
|
||||
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
|
||||
NSArray<SDWebImageDownloaderOperationToken *> *tokens = [self.callbackTokens copy];
|
||||
for (SDWebImageDownloaderOperationToken *token in tokens) {
|
||||
if (token.progressBlock) {
|
||||
token.progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
|
||||
}
|
||||
}
|
||||
__block typeof(self) strongSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
@ -275,7 +304,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
|
|||
|
||||
- (void)reset {
|
||||
@synchronized (self) {
|
||||
[self.callbackBlocks removeAllObjects];
|
||||
[self.callbackTokens removeAllObjects];
|
||||
self.dataTask = nil;
|
||||
|
||||
if (self.ownedSession) {
|
||||
|
@ -374,8 +403,14 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
}
|
||||
|
||||
if (valid) {
|
||||
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
|
||||
progressBlock(0, expected, self.request.URL);
|
||||
NSArray<SDWebImageDownloaderOperationToken *> *tokens;
|
||||
@synchronized (self) {
|
||||
tokens = [self.callbackTokens copy];
|
||||
}
|
||||
for (SDWebImageDownloaderOperationToken *token in tokens) {
|
||||
if (token.progressBlock) {
|
||||
token.progressBlock(0, expected, self.request.URL);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Status code invalid and marked as cancelled. Do not call `[self.dataTask cancel]` which may mass up URLSession life cycle
|
||||
|
@ -398,10 +433,16 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
[self.imageData appendData:data];
|
||||
|
||||
self.receivedSize = self.imageData.length;
|
||||
NSArray<SDWebImageDownloaderOperationToken *> *tokens;
|
||||
@synchronized (self) {
|
||||
tokens = [self.callbackTokens copy];
|
||||
}
|
||||
if (self.expectedSize == 0) {
|
||||
// Unknown expectedSize, immediately call progressBlock and return
|
||||
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
|
||||
progressBlock(self.receivedSize, self.expectedSize, self.request.URL);
|
||||
for (SDWebImageDownloaderOperationToken *token in tokens) {
|
||||
if (token.progressBlock) {
|
||||
token.progressBlock(self.receivedSize, self.expectedSize, self.request.URL);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -420,6 +461,8 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
|
||||
// Using data decryptor will disable the progressive decoding, since there are no support for progressive decrypt
|
||||
BOOL supportProgressive = (self.options & SDWebImageDownloaderProgressiveLoad) && !self.decryptor;
|
||||
// When multiple thumbnail decoding use different size, this progressive decoding will cause issue because each callback assume called with different size's image, can not share the same decoding part
|
||||
// We currently only pick the first thumbnail size, see #3423 talks
|
||||
// Progressive decoding Only decode partial image, full image in `URLSession:task:didCompleteWithError:`
|
||||
if (supportProgressive && !finished) {
|
||||
// Get the image data
|
||||
|
@ -444,8 +487,10 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
}
|
||||
}
|
||||
|
||||
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
|
||||
progressBlock(self.receivedSize, self.expectedSize, self.request.URL);
|
||||
for (SDWebImageDownloaderOperationToken *token in tokens) {
|
||||
if (token.progressBlock) {
|
||||
token.progressBlock(self.receivedSize, self.expectedSize, self.request.URL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -471,7 +516,9 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
// If we already cancel the operation or anything mark the operation finished, don't callback twice
|
||||
if (self.isFinished) return;
|
||||
|
||||
@synchronized(self) {
|
||||
NSArray<SDWebImageDownloaderOperationToken *> *tokens;
|
||||
@synchronized (self) {
|
||||
tokens = [self.callbackTokens copy];
|
||||
self.dataTask = nil;
|
||||
__block typeof(self) strongSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
@ -491,7 +538,7 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
[self callCompletionBlocksWithError:error];
|
||||
[self done];
|
||||
} else {
|
||||
if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
|
||||
if (tokens.count > 0) {
|
||||
NSData *imageData = self.imageData;
|
||||
self.imageData = nil;
|
||||
// data decryptor
|
||||
|
@ -514,28 +561,64 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
// decode the image in coder queue, cancel all previous decoding process
|
||||
[self.coderQueue cancelAllOperations];
|
||||
@weakify(self);
|
||||
[self.coderQueue addOperationWithBlock:^{
|
||||
@strongify(self);
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
// check if we already use progressive decoding, use that to produce faster decoding
|
||||
id<SDProgressiveImageCoder> progressiveCoder = SDImageLoaderGetProgressiveCoder(self);
|
||||
UIImage *image;
|
||||
if (progressiveCoder) {
|
||||
image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, YES, self, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
|
||||
} else {
|
||||
image = SDImageLoaderDecodeImageData(imageData, self.request.URL, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
|
||||
}
|
||||
CGSize imageSize = image.size;
|
||||
if (imageSize.width == 0 || imageSize.height == 0) {
|
||||
NSString *description = image == nil ? @"Downloaded image decode failed" : @"Downloaded image has 0 pixels";
|
||||
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : description}]];
|
||||
} else {
|
||||
[self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
|
||||
}
|
||||
[self done];
|
||||
}];
|
||||
for (SDWebImageDownloaderOperationToken *token in tokens) {
|
||||
[self.coderQueue addOperationWithBlock:^{
|
||||
@strongify(self);
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
UIImage *image;
|
||||
// check if we already decode this variant of image for current callback
|
||||
if (token.decodeOptions) {
|
||||
image = [self.imageMap objectForKey:token.decodeOptions];
|
||||
}
|
||||
if (!image) {
|
||||
// check if we already use progressive decoding, use that to produce faster decoding
|
||||
id<SDProgressiveImageCoder> progressiveCoder = SDImageLoaderGetProgressiveCoder(self);
|
||||
SDWebImageOptions options = [[self class] imageOptionsFromDownloaderOptions:self.options];
|
||||
SDWebImageContext *context;
|
||||
if (token.decodeOptions) {
|
||||
SDWebImageMutableContext *mutableContext = [NSMutableDictionary dictionaryWithDictionary:self.context];
|
||||
SDSetDecodeOptionsToContext(mutableContext, &options, token.decodeOptions);
|
||||
context = [mutableContext copy];
|
||||
} else {
|
||||
context = self.context;
|
||||
}
|
||||
if (progressiveCoder) {
|
||||
image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, YES, self, options, context);
|
||||
} else {
|
||||
image = SDImageLoaderDecodeImageData(imageData, self.request.URL, options, context);
|
||||
}
|
||||
if (image && token.decodeOptions) {
|
||||
[self.imageMap setObject:image forKey:token.decodeOptions];
|
||||
}
|
||||
}
|
||||
CGSize imageSize = image.size;
|
||||
if (imageSize.width == 0 || imageSize.height == 0) {
|
||||
NSString *description = image == nil ? @"Downloaded image decode failed" : @"Downloaded image has 0 pixels";
|
||||
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : description}]];
|
||||
} else {
|
||||
[self callCompletionBlockWithToken:token image:image imageData:imageData error:nil finished:YES];
|
||||
}
|
||||
}];
|
||||
}
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[self.coderQueue addBarrierBlock:^{
|
||||
@strongify(self);
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
[self done];
|
||||
}];
|
||||
} else {
|
||||
dispatch_barrier_async(self.coderQueue.underlyingQueue, ^{
|
||||
@strongify(self);
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
[self done];
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
|
||||
|
@ -604,13 +687,30 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
}
|
||||
|
||||
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image
|
||||
imageData:(nullable NSData *)imageData
|
||||
error:(nullable NSError *)error
|
||||
finished:(BOOL)finished {
|
||||
NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
|
||||
imageData:(nullable NSData *)imageData
|
||||
error:(nullable NSError *)error
|
||||
finished:(BOOL)finished {
|
||||
NSArray<SDWebImageDownloaderOperationToken *> *tokens;
|
||||
@synchronized (self) {
|
||||
tokens = [self.callbackTokens copy];
|
||||
}
|
||||
for (SDWebImageDownloaderOperationToken *token in tokens) {
|
||||
dispatch_main_async_safe(^{
|
||||
if (token.completedBlock) {
|
||||
token.completedBlock(image, imageData, error, finished);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)callCompletionBlockWithToken:(nonnull SDWebImageDownloaderOperationToken *)token
|
||||
image:(nullable UIImage *)image
|
||||
imageData:(nullable NSData *)imageData
|
||||
error:(nullable NSError *)error
|
||||
finished:(BOOL)finished {
|
||||
dispatch_main_async_safe(^{
|
||||
for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
|
||||
completedBlock(image, imageData, error, finished);
|
||||
if (token.completedBlock) {
|
||||
token.completedBlock(image, imageData, error, finished);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -228,7 +228,19 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
// Preprocess the options and context arg to decide the final the result for manager
|
||||
SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
|
||||
|
||||
// Start the entry to load image from cache
|
||||
// Start the entry to load image from cache, the longest steps are below
|
||||
// Steps without transformer:
|
||||
// 1. query image from cache, miss
|
||||
// 2. download data and image
|
||||
// 3. store image to cache
|
||||
|
||||
// Steps with transformer:
|
||||
// 1. query transformed image from cache, miss
|
||||
// 2. query original image from cache, miss
|
||||
// 3. download data and image
|
||||
// 4. do transform in CPU
|
||||
// 5. store original image to cache
|
||||
// 6. store transformed image to cache
|
||||
[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
|
||||
|
||||
return operation;
|
||||
|
@ -289,6 +301,7 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
// Check whether we should query cache
|
||||
BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
|
||||
if (shouldQueryCache) {
|
||||
// transformed cache key
|
||||
NSString *key = [self cacheKeyForURL:url context:context];
|
||||
@weakify(operation);
|
||||
operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
|
||||
|
@ -299,7 +312,8 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
[self safelyRemoveOperationFromRunning:operation];
|
||||
return;
|
||||
} else if (!cachedImage) {
|
||||
BOOL mayInOriginalCache = context[SDWebImageContextImageTransformer] || context[SDWebImageContextImageThumbnailPixelSize];
|
||||
NSString *originKey = [self originalCacheKeyForURL:url context:context];
|
||||
BOOL mayInOriginalCache = ![key isEqualToString:originKey];
|
||||
// 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) {
|
||||
|
@ -307,7 +321,6 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue download process
|
||||
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
|
||||
}];
|
||||
|
@ -345,7 +358,7 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
// Check whether we should query original cache
|
||||
BOOL shouldQueryOriginalCache = (originalQueryCacheType != SDImageCacheTypeNone);
|
||||
if (shouldQueryOriginalCache) {
|
||||
// Get original cache key generation without transformer/thumbnail
|
||||
// Get original cache key generation without transformer
|
||||
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) {
|
||||
|
@ -361,8 +374,8 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
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 cacheType:cacheType finished:YES completed:completedBlock];
|
||||
// Skip downloading and continue transform process, and ignore .refreshCached option for now
|
||||
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:cachedImage originalData:cachedData cacheType:cacheType finished:YES completed:completedBlock];
|
||||
|
||||
[self safelyRemoveOperationFromRunning:operation];
|
||||
}];
|
||||
|
@ -446,8 +459,8 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
[self.failedURLs removeObject:url];
|
||||
SD_UNLOCK(self->_failedURLsLock);
|
||||
}
|
||||
// Continue store cache process
|
||||
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData cacheType:SDImageCacheTypeNone finished:finished completed:completedBlock];
|
||||
// Continue transform process
|
||||
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:SDImageCacheTypeNone finished:finished completed:completedBlock];
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
|
@ -464,89 +477,6 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
}
|
||||
}
|
||||
|
||||
// Store cache process
|
||||
- (void)callStoreCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
|
||||
url:(nonnull NSURL *)url
|
||||
options:(SDWebImageOptions)options
|
||||
context:(SDWebImageContext *)context
|
||||
downloadedImage:(nullable UIImage *)downloadedImage
|
||||
downloadedData:(nullable NSData *)downloadedData
|
||||
cacheType:(SDImageCacheType)cacheType
|
||||
finished:(BOOL)finished
|
||||
completed:(nullable SDInternalCompletionBlock)completedBlock {
|
||||
// Grab the image cache to use, choose standalone original cache firstly
|
||||
id<SDImageCache> imageCache;
|
||||
if ([context[SDWebImageContextOriginalImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
|
||||
imageCache = context[SDWebImageContextOriginalImageCache];
|
||||
} else {
|
||||
// if no standalone cache available, use default cache
|
||||
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];
|
||||
}
|
||||
// the original store image cache type
|
||||
SDImageCacheType originalStoreCacheType = SDImageCacheTypeDisk;
|
||||
if (context[SDWebImageContextOriginalStoreCacheType]) {
|
||||
originalStoreCacheType = [context[SDWebImageContextOriginalStoreCacheType] integerValue];
|
||||
}
|
||||
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
|
||||
if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
|
||||
transformer = nil;
|
||||
}
|
||||
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
|
||||
|
||||
// transformer check
|
||||
BOOL shouldTransformImage = downloadedImage && transformer;
|
||||
shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
|
||||
shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isVector || (options & SDWebImageTransformVectorImage));
|
||||
// thumbnail check
|
||||
BOOL shouldThumbnailImage = context[SDWebImageContextImageThumbnailPixelSize] != nil || downloadedImage.sd_decodeOptions[SDImageCoderDecodeThumbnailPixelSize] != nil;
|
||||
|
||||
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 || shouldThumbnailImage) ? originalStoreCacheType : storeCacheType;
|
||||
UIImage *fullSizeImage = downloadedImage;
|
||||
if (shouldThumbnailImage) {
|
||||
// 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`
|
||||
fullSizeImage = nil;
|
||||
}
|
||||
if (fullSizeImage && cacheSerializer && (targetStoreCacheType == SDImageCacheTypeDisk || targetStoreCacheType == SDImageCacheTypeAll)) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
@autoreleasepool {
|
||||
NSData *cacheData = [cacheSerializer cacheDataWithImage:fullSizeImage originalData:downloadedData imageURL:url];
|
||||
[self storeImage:fullSizeImage 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 cacheType:cacheType finished:finished completed:completedBlock];
|
||||
}];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
[self storeImage:fullSizeImage 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 cacheType:cacheType finished:finished completed:completedBlock];
|
||||
}];
|
||||
}
|
||||
} else {
|
||||
// Continue transform process
|
||||
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:cacheType finished:finished completed:completedBlock];
|
||||
}
|
||||
}
|
||||
|
||||
// Transform process
|
||||
- (void)callTransformProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
|
||||
url:(nonnull NSURL *)url
|
||||
|
@ -561,88 +491,112 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
|
||||
transformer = nil;
|
||||
}
|
||||
|
||||
// transformer check
|
||||
BOOL shouldTransformImage = originalImage && transformer;
|
||||
shouldTransformImage = shouldTransformImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
|
||||
shouldTransformImage = shouldTransformImage && (!originalImage.sd_isVector || (options & SDWebImageTransformVectorImage));
|
||||
|
||||
// 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;
|
||||
}
|
||||
CGSize thumbnailSize = CGSizeZero;
|
||||
NSValue *thumbnailSizeValue = originalImage.sd_decodeOptions[SDImageCoderDecodeThumbnailPixelSize];
|
||||
if (thumbnailSizeValue != nil) {
|
||||
#if SD_MAC
|
||||
thumbnailSize = thumbnailSizeValue.sizeValue;
|
||||
#else
|
||||
thumbnailSize = thumbnailSizeValue.CGSizeValue;
|
||||
#endif
|
||||
}
|
||||
BOOL shouldEncodeThumbnail = thumbnailSize.width > 0 && thumbnailSize.height > 0;
|
||||
NSData *cacheData = originalData;
|
||||
UIImage *cacheImage = originalImage;
|
||||
if (shouldEncodeThumbnail) {
|
||||
cacheData = nil; // thumbnail don't store full size data
|
||||
originalImage = nil; // thumbnail don't have full size image
|
||||
}
|
||||
|
||||
if (shouldTransformImage) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
@autoreleasepool {
|
||||
// transformed/thumbnailed cache key
|
||||
// transformed cache key
|
||||
NSString *key = [self cacheKeyForURL:url context:context];
|
||||
// Case that transformer one thumbnail, which this time need full pixel image
|
||||
UIImage *fullSizeImage = originalImage;
|
||||
BOOL imageWasRedecoded = NO;
|
||||
if (shouldRedecodeFullImage) {
|
||||
fullSizeImage = SDImageCacheDecodeImageData(originalData, key, options, context);
|
||||
if (fullSizeImage) {
|
||||
imageWasRedecoded = YES;
|
||||
} else {
|
||||
imageWasRedecoded = NO;
|
||||
fullSizeImage = originalImage; // Fallback
|
||||
}
|
||||
}
|
||||
UIImage *transformedImage = [transformer transformedImageWithImage:fullSizeImage forKey:key];
|
||||
if (transformedImage && finished) {
|
||||
BOOL imageWasTransformed = ![transformedImage isEqual:fullSizeImage];
|
||||
// Continue store transform cache process
|
||||
[self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:transformedImage data:originalData cacheType:cacheType finished:finished transformed:imageWasTransformed || imageWasRedecoded completed:completedBlock];
|
||||
// Case that transformer on thumbnail, which this time need full pixel image
|
||||
UIImage *transformedImage = [transformer transformedImageWithImage:cacheImage forKey:key];
|
||||
if (transformedImage) {
|
||||
transformedImage.sd_isTransformed = YES;
|
||||
[self callStoreOriginCacheProcessForOperation:operation url:url options:options context:context originalImage:originalImage cacheImage:transformedImage originalData:originalData cacheData:nil cacheType:cacheType finished:finished completed:completedBlock];
|
||||
} else {
|
||||
// Continue store transform cache process
|
||||
[self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:fullSizeImage data:originalData cacheType:cacheType finished:finished transformed:imageWasRedecoded completed:completedBlock];
|
||||
[self callStoreOriginCacheProcessForOperation:operation url:url options:options context:context originalImage:originalImage cacheImage:cacheImage originalData:originalData cacheData:cacheData cacheType:cacheType 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);
|
||||
BOOL imageWasRedecoded = NO;
|
||||
if (fullSizeImage) {
|
||||
imageWasRedecoded = YES;
|
||||
} else {
|
||||
imageWasRedecoded = NO;
|
||||
fullSizeImage = originalImage; // Fallback
|
||||
}
|
||||
// Continue store transform cache process
|
||||
[self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:fullSizeImage data:originalData cacheType:cacheType finished:finished transformed:imageWasRedecoded completed:completedBlock];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Continue store transform cache process
|
||||
[self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:originalImage data:originalData cacheType:cacheType finished:finished transformed:NO completed:completedBlock];
|
||||
[self callStoreOriginCacheProcessForOperation:operation url:url options:options context:context originalImage:originalImage cacheImage:cacheImage originalData:originalData cacheData:cacheData cacheType:cacheType 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
|
||||
finished:(BOOL)finished
|
||||
transformed:(BOOL)transformed
|
||||
completed:(nullable SDInternalCompletionBlock)completedBlock {
|
||||
// Store origin cache process
|
||||
- (void)callStoreOriginCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
|
||||
url:(nonnull NSURL *)url
|
||||
options:(SDWebImageOptions)options
|
||||
context:(SDWebImageContext *)context
|
||||
originalImage:(nullable UIImage *)originalImage
|
||||
cacheImage:(nullable UIImage *)cacheImage
|
||||
originalData:(nullable NSData *)originalData
|
||||
cacheData:(nullable NSData *)cacheData
|
||||
cacheType:(SDImageCacheType)cacheType
|
||||
finished:(BOOL)finished
|
||||
completed:(nullable SDInternalCompletionBlock)completedBlock {
|
||||
// Grab the image cache to use, choose standalone original cache firstly
|
||||
id<SDImageCache> imageCache;
|
||||
if ([context[SDWebImageContextOriginalImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
|
||||
imageCache = context[SDWebImageContextOriginalImageCache];
|
||||
} else {
|
||||
// if no standalone cache available, use default cache
|
||||
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
|
||||
imageCache = context[SDWebImageContextImageCache];
|
||||
} else {
|
||||
imageCache = self.imageCache;
|
||||
}
|
||||
}
|
||||
BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
|
||||
// the original store image cache type
|
||||
SDImageCacheType originalStoreCacheType = SDImageCacheTypeDisk;
|
||||
if (context[SDWebImageContextOriginalStoreCacheType]) {
|
||||
originalStoreCacheType = [context[SDWebImageContextOriginalStoreCacheType] integerValue];
|
||||
}
|
||||
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
|
||||
|
||||
// Get original cache key generation without transformer
|
||||
NSString *key = [self originalCacheKeyForURL:url context:context];
|
||||
if (finished && cacheSerializer && (originalStoreCacheType == SDImageCacheTypeDisk || originalStoreCacheType == SDImageCacheTypeAll)) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
@autoreleasepool {
|
||||
NSData *newOriginalData = [cacheSerializer cacheDataWithImage:originalImage originalData:originalData imageURL:url];
|
||||
// Store original image and data
|
||||
[self storeImage:originalImage imageData:newOriginalData forKey:key imageCache:imageCache cacheType:originalStoreCacheType finished:finished waitStoreCache:waitStoreCache completion:^{
|
||||
// Continue store cache process, transformed data is nil
|
||||
[self callStoreCacheProcessForOperation:operation url:url options:options context:context image:cacheImage data:cacheData cacheType:cacheType finished:finished completed:completedBlock];
|
||||
}];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Store original image and data
|
||||
[self storeImage:originalImage imageData:originalData forKey:key imageCache:imageCache cacheType:originalStoreCacheType finished:finished waitStoreCache:waitStoreCache completion:^{
|
||||
// Continue store cache process, transformed data is nil
|
||||
[self callStoreCacheProcessForOperation:operation url:url options:options context:context image:cacheImage data:cacheData cacheType:cacheType finished:finished completed:completedBlock];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
// Store normal cache process
|
||||
- (void)callStoreCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
|
||||
url:(nonnull NSURL *)url
|
||||
options:(SDWebImageOptions)options
|
||||
context:(SDWebImageContext *)context
|
||||
image:(nullable UIImage *)image
|
||||
data:(nullable NSData *)data
|
||||
cacheType:(SDImageCacheType)cacheType
|
||||
finished:(BOOL)finished
|
||||
completed:(nullable SDInternalCompletionBlock)completedBlock {
|
||||
// Grab the image cache to use
|
||||
id<SDImageCache> imageCache;
|
||||
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
|
||||
|
@ -657,25 +611,22 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
|
||||
}
|
||||
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
|
||||
// thumbnail check
|
||||
BOOL shouldThumbnailImage = context[SDWebImageContextImageThumbnailPixelSize] != nil || image.sd_decodeOptions[SDImageCoderDecodeThumbnailPixelSize] != nil;
|
||||
|
||||
// Store the transformed/thumbnail image into the cache
|
||||
if (image && (transformed || shouldThumbnailImage)) {
|
||||
NSData *cacheData;
|
||||
// pass nil if the image was transformed/thumbnailed, so we can recalculate the data from the image
|
||||
if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
|
||||
cacheData = [cacheSerializer cacheDataWithImage:image originalData:nil imageURL:url];
|
||||
} else {
|
||||
cacheData = nil;
|
||||
}
|
||||
// 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:^{
|
||||
// transformed cache key
|
||||
NSString *key = [self cacheKeyForURL:url context:context];
|
||||
if (finished && cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
NSData *newData = [cacheSerializer cacheDataWithImage:image originalData:data imageURL:url];
|
||||
// Store image and data
|
||||
[self storeImage:image imageData:newData forKey:key imageCache:imageCache cacheType:storeCacheType finished:finished waitStoreCache:waitStoreCache completion:^{
|
||||
[self callCompletionBlockForOperation:operation completion:completedBlock image:image data:data error:nil cacheType:cacheType finished:finished url:url];
|
||||
}];
|
||||
});
|
||||
} else {
|
||||
// Store image and data
|
||||
[self storeImage:image imageData:data forKey:key imageCache:imageCache cacheType:storeCacheType finished:finished 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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -695,8 +646,16 @@ static id<SDImageLoader> _defaultImageLoader;
|
|||
forKey:(nullable NSString *)key
|
||||
imageCache:(nonnull id<SDImageCache>)imageCache
|
||||
cacheType:(SDImageCacheType)cacheType
|
||||
finished:(BOOL)finished
|
||||
waitStoreCache:(BOOL)waitStoreCache
|
||||
completion:(nullable SDWebImageNoParamsBlock)completion {
|
||||
// Ignore progressive data cache
|
||||
if (!finished) {
|
||||
if (completion) {
|
||||
completion();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 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) {
|
||||
|
|
|
@ -66,11 +66,17 @@
|
|||
*/
|
||||
@property (nonatomic, assign) BOOL sd_isIncremental;
|
||||
|
||||
/**
|
||||
A bool value indicating that the image is transformed from original image, so the image data may not always match original download one.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL sd_isTransformed;
|
||||
|
||||
/**
|
||||
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.
|
||||
@note This is used to identify and check the image is from thumbnail decoding, and the callback's data **will be nil** (because this time the data saved to disk does not match the image return to you. If you need full size data, query the cache with full size url key)
|
||||
@warning You should not store object inside which keep strong reference to image itself, which will cause retain cycle.
|
||||
@warning This API exist only because of current SDWebImageDownloader bad design which does not callback the context we call it. There will be refactor in future (API break), use with caution.
|
||||
*/
|
||||
@property (nonatomic, copy) SDImageCoderOptions *sd_decodeOptions;
|
||||
|
||||
|
|
|
@ -184,6 +184,15 @@
|
|||
return value.boolValue;
|
||||
}
|
||||
|
||||
- (void)setSd_isTransformed:(BOOL)sd_isTransformed {
|
||||
objc_setAssociatedObject(self, @selector(sd_isTransformed), @(sd_isTransformed), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
- (BOOL)sd_isTransformed {
|
||||
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_isTransformed));
|
||||
return value.boolValue;
|
||||
}
|
||||
|
||||
- (void)setSd_decodeOptions:(SDImageCoderOptions *)sd_decodeOptions {
|
||||
objc_setAssociatedObject(self, @selector(sd_decodeOptions), sd_decodeOptions, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
}
|
||||
|
|
|
@ -768,6 +768,30 @@
|
|||
}];
|
||||
}
|
||||
|
||||
- (void)test30ThatDifferentThumbnailLoadShouldCallbackDifferentSize {
|
||||
// We move the logic into SDWebImageDownloaderOperation, which decode each callback's thumbnail size with different decoding pipeline, and callback independently
|
||||
// Note the progressiveLoad does not support this and always callback first size
|
||||
|
||||
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];
|
||||
[SDWebImageDownloader.sharedDownloader downloadImageWithURL:url options:0 context:@{SDWebImageContextImageThumbnailPixelSize : @(thumbnailSize)} progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
|
||||
expect(image.size).equal(thumbnailSize);
|
||||
|
||||
[expectation fulfill];
|
||||
}];
|
||||
}
|
||||
|
||||
[self waitForExpectationsWithTimeout:kAsyncTestTimeout * 5 handler:nil];
|
||||
}
|
||||
|
||||
#pragma mark - SDWebImageLoader
|
||||
- (void)test30CustomImageLoaderWorks {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Custom image not works"];
|
||||
|
|
|
@ -389,6 +389,7 @@
|
|||
[SDWebImageManager.sharedManager loadImageWithURL:url options:SDWebImageFromCacheOnly context:@{SDWebImageContextImageTransformer : transformer, SDWebImageContextOriginalQueryCacheType : @(SDImageCacheTypeAll)} progress:nil completed:^(UIImage * _Nullable image2, NSData * _Nullable data2, NSError * _Nullable error2, SDImageCacheType cacheType2, BOOL finished2, NSURL * _Nullable imageURL2) {
|
||||
// Get the transformed image
|
||||
expect(image2).equal(transformer.testImage);
|
||||
expect(data).beNil(); // Currently, the thumbnailed and transformed image always data is nil, to avoid confuse user (the image and data represent no longer match)
|
||||
[SDImageCache.sharedImageCache removeImageFromMemoryForKey:originalKey];
|
||||
[SDImageCache.sharedImageCache removeImageFromDiskForKey:originalKey];
|
||||
[expectation fulfill];
|
||||
|
@ -417,6 +418,7 @@
|
|||
SDWebImageContextStoreCacheType: @(SDImageCacheTypeMemory)} progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
|
||||
// Get the transformed image
|
||||
expect(image).equal(transformer.testImage);
|
||||
expect(data).beNil(); // Currently, the thumbnailed and transformed image always data is nil, to avoid confuse user (the image and data represent no longer match)
|
||||
// Now, the original image is stored into originalCache
|
||||
UIImage *originalImage = [originalCache imageFromMemoryCacheForKey:originalKey];
|
||||
expect(originalImage.size).equal(CGSizeMake(103, 103));
|
||||
|
@ -487,7 +489,8 @@
|
|||
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];
|
||||
NSData *fullSizeData = fullSizeImage.sd_imageData;
|
||||
[SDImageCache.sharedImageCache storeImageDataToDisk:fullSizeData forKey:fullSizeKey];
|
||||
|
||||
CGSize thumbnailSize = CGSizeMake(100, 100);
|
||||
NSString *thumbnailKey = SDThumbnailedKeyForKey(fullSizeKey, thumbnailSize, YES);
|
||||
|
@ -495,6 +498,7 @@
|
|||
// 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(data).beNil(); // Currently, the thumbnailed and transformed image always data is nil, to avoid confuse user (the image and data represent no longer match)
|
||||
expect(cacheType).equal(SDImageCacheTypeDisk);
|
||||
expect(finished).beTruthy();
|
||||
|
||||
|
@ -510,8 +514,8 @@
|
|||
|
||||
- (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
|
||||
// We move the logic into SDWebImageDownloaderOperation, which decode each callback's thumbnail size with different decoding pipeline, and callback independently
|
||||
// Note the progressiveLoad does not support this and always callback first size
|
||||
|
||||
NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/501x501.png"];
|
||||
NSString *fullSizeKey = [SDWebImageManager.sharedManager cacheKeyForURL:url];
|
||||
|
@ -526,6 +530,7 @@
|
|||
__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(data).beNil(); // Currently, the thumbnailed and transformed image always data is nil, to avoid confuse user (the image and data represent no longer match)
|
||||
expect(cacheType).equal(SDImageCacheTypeNone);
|
||||
expect(finished).beTruthy();
|
||||
|
||||
|
|
|
@ -67,6 +67,12 @@
|
|||
|
||||
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
||||
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
|
||||
return [self addHandlersForProgress:progressBlock completed:completedBlock decodeOptions:nil];
|
||||
}
|
||||
|
||||
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
||||
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock
|
||||
decodeOptions:(nullable SDImageCoderOptions *)decodeOptions {
|
||||
if (completedBlock) {
|
||||
[self.completedBlocks addObject:completedBlock];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue