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); - }); + }]; } }