From 43ec4726e112ce7cb85b28b3751f1e07492801ef Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 4 Jan 2023 18:47:50 +0800 Subject: [PATCH] Added context option `callbackQueue` for advanced user to control which queue to callback This is used for user who call SDWebImage outside from main queue and need precise queue control, such as avoid chain queue blocking (like AVKit lazy load) --- SDWebImage.xcodeproj/project.pbxproj | 8 +++ SDWebImage/Core/SDCallbackQueue.h | 32 +++++++++++ SDWebImage/Core/SDCallbackQueue.m | 18 +++++++ SDWebImage/Core/SDImageCache.m | 53 +++++++------------ SDWebImage/Core/SDImageCacheDefine.h | 19 +++++++ SDWebImage/Core/SDWebImageDefine.h | 5 ++ SDWebImage/Core/SDWebImageDefine.m | 1 + .../Core/SDWebImageDownloaderOperation.m | 17 +++--- SDWebImage/Core/SDWebImageManager.m | 37 +++++++------ 9 files changed, 132 insertions(+), 58 deletions(-) create mode 100644 SDWebImage/Core/SDCallbackQueue.h create mode 100644 SDWebImage/Core/SDCallbackQueue.m diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index 3e7c95af..50e72bd2 100644 --- a/SDWebImage.xcodeproj/project.pbxproj +++ b/SDWebImage.xcodeproj/project.pbxproj @@ -25,6 +25,8 @@ 320CAE172086F50500CFFC80 /* SDWebImageError.h in Headers */ = {isa = PBXBuildFile; fileRef = 320CAE132086F50500CFFC80 /* SDWebImageError.h */; settings = {ATTRIBUTES = (Public, ); }; }; 320CAE1B2086F50500CFFC80 /* SDWebImageError.m in Sources */ = {isa = PBXBuildFile; fileRef = 320CAE142086F50500CFFC80 /* SDWebImageError.m */; }; 320CAE1D2086F50500CFFC80 /* SDWebImageError.m in Sources */ = {isa = PBXBuildFile; fileRef = 320CAE142086F50500CFFC80 /* SDWebImageError.m */; }; + 321117A9296573680001FC2C /* SDCallbackQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 321117A7296573680001FC2C /* SDCallbackQueue.h */; }; + 321117AA296573680001FC2C /* SDCallbackQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 321117A8296573680001FC2C /* SDCallbackQueue.m */; }; 321B37832083290E00C0EA77 /* SDImageLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B377D2083290D00C0EA77 /* SDImageLoader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 321B37872083290E00C0EA77 /* SDImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B377E2083290D00C0EA77 /* SDImageLoader.m */; }; 321B37892083290E00C0EA77 /* SDImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B377E2083290D00C0EA77 /* SDImageLoader.m */; }; @@ -387,6 +389,8 @@ 320224BA203979BA00E9F285 /* SDAnimatedImageRep.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImageRep.m; path = Core/SDAnimatedImageRep.m; sourceTree = ""; }; 320CAE132086F50500CFFC80 /* SDWebImageError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageError.h; path = Core/SDWebImageError.h; sourceTree = ""; }; 320CAE142086F50500CFFC80 /* SDWebImageError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageError.m; path = Core/SDWebImageError.m; sourceTree = ""; }; + 321117A7296573680001FC2C /* SDCallbackQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDCallbackQueue.h; path = Core/SDCallbackQueue.h; sourceTree = ""; }; + 321117A8296573680001FC2C /* SDCallbackQueue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDCallbackQueue.m; path = Core/SDCallbackQueue.m; sourceTree = ""; }; 321B377D2083290D00C0EA77 /* SDImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDImageLoader.h; path = Core/SDImageLoader.h; sourceTree = ""; }; 321B377E2083290D00C0EA77 /* SDImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDImageLoader.m; path = Core/SDImageLoader.m; sourceTree = ""; }; 321B377F2083290E00C0EA77 /* SDImageLoadersManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDImageLoadersManager.h; path = Core/SDImageLoadersManager.h; sourceTree = ""; }; @@ -872,6 +876,8 @@ 325312C7200F09910046BF1E /* SDWebImageTransition.m */, 32C0FDDF2013426C001B8F2D /* SDWebImageIndicator.h */, 32C0FDE02013426C001B8F2D /* SDWebImageIndicator.m */, + 321117A7296573680001FC2C /* SDCallbackQueue.h */, + 321117A8296573680001FC2C /* SDCallbackQueue.m */, ); name = Utils; sourceTree = ""; @@ -957,6 +963,7 @@ 3250C9EE2355D9DA0093A896 /* SDWebImageDownloaderDecryptor.h in Headers */, 32F7C0862030719600873181 /* UIImage+Transform.h in Headers */, 321E60C01F38E91700405457 /* UIImage+ForceDecode.h in Headers */, + 321117A9296573680001FC2C /* SDCallbackQueue.h in Headers */, 329F1243223FAD3400B309FD /* SDInternalMacros.h in Headers */, 80B6DF7F2142B43300BCB334 /* NSImage+Compatibility.h in Headers */, 32C0FDE32013426C001B8F2D /* SDWebImageIndicator.h in Headers */, @@ -1205,6 +1212,7 @@ 4A2CAE221AB4BB7000B6BC39 /* SDWebImageManager.m in Sources */, 4A2CAE191AB4BB6400B6BC39 /* SDWebImageCompat.m in Sources */, 325C460B22339426004CAE11 /* SDWeakProxy.m in Sources */, + 321117AA296573680001FC2C /* SDCallbackQueue.m in Sources */, 321B37892083290E00C0EA77 /* SDImageLoader.m in Sources */, 32484771201775F600AF9E5A /* SDAnimatedImage.m in Sources */, 807A12301F89636300EC2A9B /* SDImageCodersManager.m in Sources */, diff --git a/SDWebImage/Core/SDCallbackQueue.h b/SDWebImage/Core/SDCallbackQueue.h new file mode 100644 index 00000000..bacfe400 --- /dev/null +++ b/SDWebImage/Core/SDCallbackQueue.h @@ -0,0 +1,32 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + + +#import "SDWebImageDefine.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SDCallbackQueue : NSObject + +@property (nonnull, class, readonly) SDCallbackQueue *mainQueue; + +@property (nonnull, class, readonly) SDCallbackQueue *callerQueue; + +@property (nonnull, class, readonly) SDCallbackQueue *globalQueue; + ++ (SDCallbackQueue *)dispatchQueue:(dispatch_queue_t)queue; + +- (void)sync:(SDWebImageNoParamsBlock)block; + +- (void)async:(SDWebImageNoParamsBlock)block; + +- (void)asyncSafe:(SDWebImageNoParamsBlock)block; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SDWebImage/Core/SDCallbackQueue.m b/SDWebImage/Core/SDCallbackQueue.m new file mode 100644 index 00000000..08e2c75a --- /dev/null +++ b/SDWebImage/Core/SDCallbackQueue.m @@ -0,0 +1,18 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + + +#import "SDCallbackQueue.h" + +@implementation SDCallbackQueue + +- (void)sync:(dispatch_block_t)block { + +} + +@end diff --git a/SDWebImage/Core/SDImageCache.m b/SDWebImage/Core/SDImageCache.m index 5d3c39cb..61f48d22 100644 --- a/SDWebImage/Core/SDImageCache.m +++ b/SDWebImage/Core/SDImageCache.m @@ -14,6 +14,7 @@ #import "UIImage+MemoryCacheCost.h" #import "UIImage+Metadata.h" #import "UIImage+ExtendedCacheData.h" +#import "SDCallbackQueue.h" @interface SDImageCacheToken () @@ -63,6 +64,8 @@ static NSString * _defaultDiskCacheDirectory; @property (nonatomic, copy, readwrite, nonnull) NSString *diskCachePath; @property (nonatomic, strong, nullable) dispatch_queue_t ioQueue; +- (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock; + @end @@ -197,20 +200,20 @@ static NSString * _defaultDiskCacheDirectory; - (void)storeImage:(nullable UIImage *)image forKey:(nullable NSString *)key completion:(nullable SDWebImageNoParamsBlock)completionBlock { - [self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock]; + [self storeImage:image imageData:nil forKey:key options:0 context:nil cacheType:SDImageCacheTypeAll completion:completionBlock]; } - (void)storeImage:(nullable UIImage *)image forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock { - [self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock]; + [self storeImage:image imageData:nil forKey:key options:0 context:nil cacheType:(toDisk ? SDImageCacheTypeAll : SDImageCacheTypeMemory) completion:completionBlock]; } - (void)storeImageData:(nullable NSData *)imageData forKey:(nullable NSString *)key completion:(nullable SDWebImageNoParamsBlock)completionBlock { - [self storeImage:nil imageData:imageData forKey:key toDisk:YES completion:completionBlock]; + [self storeImage:nil imageData:imageData forKey:key options:0 context:nil cacheType:SDImageCacheTypeAll completion:completionBlock]; } - (void)storeImage:(nullable UIImage *)image @@ -218,14 +221,15 @@ static NSString * _defaultDiskCacheDirectory; forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock { - return [self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:toDisk completion:completionBlock]; + return [self storeImage:image imageData:imageData forKey:key options:0 context:nil cacheType:(toDisk ? SDImageCacheTypeDisk : SDImageCacheTypeMemory) completion:completionBlock]; } - (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key - toMemory:(BOOL)toMemory - toDisk:(BOOL)toDisk + options:(SDWebImageOptions)options + context:(nullable SDWebImageContext *)context + cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock { if ((!image && !imageData) || !key) { if (completionBlock) { @@ -233,6 +237,8 @@ static NSString * _defaultDiskCacheDirectory; } return; } + BOOL toMemory = cacheType == SDImageCacheTypeMemory || cacheType == SDImageCacheTypeAll; + BOOL toDisk = cacheType == SDImageCacheTypeDisk || cacheType == SDImageCacheTypeAll; // if memory cache is enabled if (image && toMemory && self.config.shouldCacheImagesInMemory) { NSUInteger cost = image.sd_memoryCost; @@ -245,6 +251,7 @@ static NSString * _defaultDiskCacheDirectory; } return; } + SDCallbackQueue *queue = context[SDWebImageContextCallbackQueue]; dispatch_async(self.ioQueue, ^{ @autoreleasepool { NSData *data = imageData; @@ -271,9 +278,9 @@ static NSString * _defaultDiskCacheDirectory; } if (completionBlock) { - dispatch_async(dispatch_get_main_queue(), ^{ + [(queue ?: SDCallbackQueue.mainQueue) async:^{ completionBlock(); - }); + }]; } }); } @@ -609,6 +616,7 @@ static NSString * _defaultDiskCacheDirectory; // 2. in-memory cache miss & diskDataSync BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) || (!image && options & SDImageCacheQueryDiskDataSync)); + SDCallbackQueue *queue = context[SDWebImageContextCallbackQueue]; NSData* (^queryDiskDataBlock)(void) = ^NSData* { @synchronized (operation) { if (operation.isCancelled) { @@ -680,7 +688,7 @@ static NSString * _defaultDiskCacheDirectory; } } if (doneBlock) { - dispatch_async(dispatch_get_main_queue(), ^{ + [(queue ?: SDCallbackQueue.mainQueue) async:^{ // Dispatch from IO queue to main queue need time, user may call cancel during the dispatch timing // This check is here to avoid double callback (one is from `SDImageCacheToken` in sync) @synchronized (operation) { @@ -689,7 +697,7 @@ static NSString * _defaultDiskCacheDirectory; } } doneBlock(diskImage, diskData, SDImageCacheTypeDisk); - }); + }]; } }); } @@ -894,30 +902,7 @@ static NSString * _defaultDiskCacheDirectory; } - (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock { - switch (cacheType) { - case SDImageCacheTypeNone: { - [self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:NO completion:completionBlock]; - } - break; - case SDImageCacheTypeMemory: { - [self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:NO completion:completionBlock]; - } - break; - case SDImageCacheTypeDisk: { - [self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:YES completion:completionBlock]; - } - break; - case SDImageCacheTypeAll: { - [self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:YES completion:completionBlock]; - } - break; - default: { - if (completionBlock) { - completionBlock(); - } - } - break; - } + [self storeImage:image imageData:imageData forKey:key options:0 context:nil cacheType:cacheType completion:completionBlock]; } - (void)removeImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock { diff --git a/SDWebImage/Core/SDImageCacheDefine.h b/SDWebImage/Core/SDImageCacheDefine.h index eb020f48..5ff05827 100644 --- a/SDWebImage/Core/SDImageCacheDefine.h +++ b/SDWebImage/Core/SDImageCacheDefine.h @@ -122,6 +122,25 @@ FOUNDATION_EXPORT void SDSetDecodeOptionsToContext(SDWebImageMutableContext * _N cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock; +/** + Store the image into image cache for the given key. If cache type is memory only, completion is called synchronously, else asynchronously. + + @param image The image to store + @param imageData The image data to be used for disk storage + @param key The image cache key + @param options A mask to specify options to use for this store + @param context The context options to use. Pass `.storeCacheType` to control cache type, pass `.callbackQueue` to control callback queue + @param cacheType The image store op cache type + @param completionBlock A block executed after the operation is finished + */ +- (void)storeImage:(nullable UIImage *)image + imageData:(nullable NSData *)imageData + forKey:(nullable NSString *)key + options:(SDWebImageOptions)options + context:(nullable SDWebImageContext *)context + cacheType:(SDImageCacheType)cacheType + completion:(nullable SDWebImageNoParamsBlock)completionBlock; + /** Remove the image from image cache for the given key. If cache type is memory only, completion is called synchronously, else asynchronously. diff --git a/SDWebImage/Core/SDWebImageDefine.h b/SDWebImage/Core/SDWebImageDefine.h index df7c7fa4..ee6bd160 100644 --- a/SDWebImage/Core/SDWebImageDefine.h +++ b/SDWebImage/Core/SDWebImageDefine.h @@ -221,6 +221,11 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextSetIma */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCustomManager API_DEPRECATED("Use individual context option like .imageCache, .imageLoader and .imageTransformer instead", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED)); +/** + + */ +FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCallbackQueue; + /** A id instance which conforms to `SDImageCache` protocol. It's used to override the image manager's cache during the image loading pipeline. In other word, if you just want to specify a custom cache during image loading, you don't need to re-create a dummy SDWebImageManager instance with the cache. If not provided, use the image manager's cache (id) diff --git a/SDWebImage/Core/SDWebImageDefine.m b/SDWebImage/Core/SDWebImageDefine.m index 651c2d53..c410908d 100644 --- a/SDWebImage/Core/SDWebImageDefine.m +++ b/SDWebImage/Core/SDWebImageDefine.m @@ -127,6 +127,7 @@ inline UIImage * _Nullable SDScaledImageForScaleFactor(CGFloat scale, UIImage * SDWebImageContextOption const SDWebImageContextSetImageOperationKey = @"setImageOperationKey"; SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager"; +SDWebImageContextOption const SDWebImageContextCallbackQueue = @"callbackQueue"; SDWebImageContextOption const SDWebImageContextImageCache = @"imageCache"; SDWebImageContextOption const SDWebImageContextImageLoader = @"imageLoader"; SDWebImageContextOption const SDWebImageContextImageCoder = @"imageCoder"; diff --git a/SDWebImage/Core/SDWebImageDownloaderOperation.m b/SDWebImage/Core/SDWebImageDownloaderOperation.m index 82c3772c..87e87cb5 100644 --- a/SDWebImage/Core/SDWebImageDownloaderOperation.m +++ b/SDWebImage/Core/SDWebImageDownloaderOperation.m @@ -12,6 +12,7 @@ #import "SDWebImageDownloaderResponseModifier.h" #import "SDWebImageDownloaderDecryptor.h" #import "SDImageCacheDefine.h" +#import "SDCallbackQueue.h" // A handler to represent individual request @interface SDWebImageDownloaderOperationToken : NSObject @@ -689,9 +690,9 @@ didReceiveResponse:(NSURLResponse *)response } - (void)callCompletionBlocksWithImage:(nullable UIImage *)image - imageData:(nullable NSData *)imageData - error:(nullable NSError *)error - finished:(BOOL)finished { + imageData:(nullable NSData *)imageData + error:(nullable NSError *)error + finished:(BOOL)finished { NSArray *tokens; @synchronized (self) { tokens = [self.callbackTokens copy]; @@ -699,9 +700,10 @@ didReceiveResponse:(NSURLResponse *)response for (SDWebImageDownloaderOperationToken *token in tokens) { SDWebImageDownloaderCompletedBlock completedBlock = token.completedBlock; if (completedBlock) { - dispatch_main_async_safe(^{ + SDCallbackQueue *queue = self.context[SDWebImageContextCallbackQueue]; + [(queue ?: SDCallbackQueue.mainQueue) asyncSafe:^{ completedBlock(image, imageData, error, finished); - }); + }]; } } } @@ -713,9 +715,10 @@ didReceiveResponse:(NSURLResponse *)response finished:(BOOL)finished { SDWebImageDownloaderCompletedBlock completedBlock = token.completedBlock; if (completedBlock) { - dispatch_main_async_safe(^{ + SDCallbackQueue *queue = self.context[SDWebImageContextCallbackQueue]; + [(queue ?: SDCallbackQueue.mainQueue) asyncSafe:^{ completedBlock(image, imageData, error, finished); - }); + }]; } } diff --git a/SDWebImage/Core/SDWebImageManager.m b/SDWebImage/Core/SDWebImageManager.m index e02a2409..7f559191 100644 --- a/SDWebImage/Core/SDWebImageManager.m +++ b/SDWebImage/Core/SDWebImageManager.m @@ -13,6 +13,7 @@ #import "SDAssociatedObject.h" #import "SDWebImageError.h" #import "SDInternalMacros.h" +#import "SDCallbackQueue.h" static id _defaultImageCache; static id _defaultImageLoader; @@ -213,11 +214,14 @@ static id _defaultImageLoader; isFailedUrl = [self.failedURLs containsObject:url]; SD_UNLOCK(_failedURLsLock); } + + // Preprocess the options and context arg to decide the final the result for manager + SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context]; if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) { NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil"; NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL; - [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] url:url]; + [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] queue:result.context[SDWebImageContextCallbackQueue] url:url]; return operation; } @@ -225,9 +229,6 @@ static id _defaultImageLoader; [self.runningOperations addObject:operation]; SD_UNLOCK(_runningOperationsLock); - // 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, the longest steps are below // Steps without transformer: // 1. query image from cache, miss @@ -306,7 +307,7 @@ static id _defaultImageLoader; @strongify(operation); if (!operation || operation.isCancelled) { // Image combined operation cancelled by user - [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url]; + [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] queue:context[SDWebImageContextCallbackQueue] url:url]; [self safelyRemoveOperationFromRunning:operation]; return; } else if (!cachedImage) { @@ -360,7 +361,7 @@ static id _defaultImageLoader; @strongify(operation); if (!operation || operation.isCancelled) { // Image combined operation cancelled by user - [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url]; + [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] queue:context[SDWebImageContextCallbackQueue] url:url]; [self safelyRemoveOperationFromRunning:operation]; return; } else if (!cachedImage) { @@ -414,7 +415,7 @@ static id _defaultImageLoader; if (cachedImage && options & SDWebImageRefreshCached) { // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. - [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; + [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES queue:context[SDWebImageContextCallbackQueue] url:url]; // Pass the cached image to the image loader. The image loader should check whether the remote image is equal to the cached image. SDWebImageMutableContext *mutableContext; if (context) { @@ -431,14 +432,14 @@ static id _defaultImageLoader; @strongify(operation); if (!operation || operation.isCancelled) { // Image combined operation cancelled by user - [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] url:url]; + [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] queue:context[SDWebImageContextCallbackQueue] url:url]; } else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) { // Image refresh hit the NSURLCache cache, do not call the completion block } else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) { // Download operation cancelled by user before sending the request, don't block failed URL - [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url]; + [self callCompletionBlockForOperation:operation completion:completedBlock error:error queue:context[SDWebImageContextCallbackQueue] url:url]; } else if (error) { - [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url]; + [self callCompletionBlockForOperation:operation completion:completedBlock error:error queue:context[SDWebImageContextCallbackQueue] url:url]; BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context]; if (shouldBlockFailedURL) { @@ -461,11 +462,11 @@ static id _defaultImageLoader; } }]; } else if (cachedImage) { - [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; + [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES queue:context[SDWebImageContextCallbackQueue] url:url]; [self safelyRemoveOperationFromRunning:operation]; } else { // Image not in cache and download disallowed by delegate - [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url]; + [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES queue:context[SDWebImageContextCallbackQueue] url:url]; [self safelyRemoveOperationFromRunning:operation]; } } @@ -605,13 +606,13 @@ static id _defaultImageLoader; 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]; + [self callCompletionBlockForOperation:operation completion:completedBlock image:image data:data error:nil cacheType:cacheType finished:finished queue:context[SDWebImageContextCallbackQueue] 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]; + [self callCompletionBlockForOperation:operation completion:completedBlock image:image data:data error:nil cacheType:cacheType finished:finished queue:context[SDWebImageContextCallbackQueue] url:url]; }]; } } @@ -660,8 +661,9 @@ static id _defaultImageLoader; - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation completion:(nullable SDInternalCompletionBlock)completionBlock error:(nullable NSError *)error + queue:(nullable SDCallbackQueue *)queue url:(nullable NSURL *)url { - [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url]; + [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES queue:queue url:url]; } - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation @@ -671,11 +673,12 @@ static id _defaultImageLoader; error:(nullable NSError *)error cacheType:(SDImageCacheType)cacheType finished:(BOOL)finished + queue:(nullable SDCallbackQueue *)queue url:(nullable NSURL *)url { if (completionBlock) { - dispatch_main_async_safe(^{ + [(queue ?: SDCallbackQueue.mainQueue) asyncSafe:^{ completionBlock(image, data, error, cacheType, finished, url); - }); + }]; } }