Merge pull request #3465 from dreampiggy/feat/callback_queue

Added context option `callbackQueue` and `SDCallbackQueue` wrapper for advanced user to control which queue to callback
This commit is contained in:
DreamPiggy 2023-01-10 14:38:00 +08:00 committed by GitHub
commit 05f7fb9b0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 464 additions and 110 deletions

View File

@ -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 */; settings = {ATTRIBUTES = (Public, ); }; };
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 */; };
@ -67,6 +69,7 @@
324DF4B6200A14DC008A84CC /* SDWebImageDefine.h in Headers */ = {isa = PBXBuildFile; fileRef = 324DF4B2200A14DC008A84CC /* SDWebImageDefine.h */; settings = {ATTRIBUTES = (Public, ); }; };
324DF4BA200A14DC008A84CC /* SDWebImageDefine.m in Sources */ = {isa = PBXBuildFile; fileRef = 324DF4B3200A14DC008A84CC /* SDWebImageDefine.m */; };
324DF4BC200A14DC008A84CC /* SDWebImageDefine.m in Sources */ = {isa = PBXBuildFile; fileRef = 324DF4B3200A14DC008A84CC /* SDWebImageDefine.m */; };
325074F2296C546D00B730CF /* SDCallbackQueue.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 321117A7296573680001FC2C /* SDCallbackQueue.h */; };
3250C9EE2355D9DA0093A896 /* SDWebImageDownloaderDecryptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 3250C9EC2355D9DA0093A896 /* SDWebImageDownloaderDecryptor.h */; settings = {ATTRIBUTES = (Public, ); }; };
3250C9EF2355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 3250C9ED2355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m */; };
3250C9F02355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 3250C9ED2355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m */; };
@ -317,6 +320,7 @@
dstPath = include/SDWebImage;
dstSubfolderSpec = 16;
files = (
325074F2296C546D00B730CF /* SDCallbackQueue.h in Copy Headers */,
32D9EE4B24AF259B00EAFDF4 /* SDImageAWebPCoder.h in Copy Headers */,
328E9DE523A61DD30051C893 /* SDGraphicsImageRenderer.h in Copy Headers */,
325F7CCD2389467800AEDFCC /* UIImage+ExtendedCacheData.h in Copy Headers */,
@ -387,6 +391,8 @@
320224BA203979BA00E9F285 /* SDAnimatedImageRep.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImageRep.m; path = Core/SDAnimatedImageRep.m; sourceTree = "<group>"; };
320CAE132086F50500CFFC80 /* SDWebImageError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageError.h; path = Core/SDWebImageError.h; sourceTree = "<group>"; };
320CAE142086F50500CFFC80 /* SDWebImageError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageError.m; path = Core/SDWebImageError.m; sourceTree = "<group>"; };
321117A7296573680001FC2C /* SDCallbackQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDCallbackQueue.h; path = Core/SDCallbackQueue.h; sourceTree = "<group>"; };
321117A8296573680001FC2C /* SDCallbackQueue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDCallbackQueue.m; path = Core/SDCallbackQueue.m; sourceTree = "<group>"; };
321B377D2083290D00C0EA77 /* SDImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDImageLoader.h; path = Core/SDImageLoader.h; sourceTree = "<group>"; };
321B377E2083290D00C0EA77 /* SDImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDImageLoader.m; path = Core/SDImageLoader.m; sourceTree = "<group>"; };
321B377F2083290E00C0EA77 /* SDImageLoadersManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDImageLoadersManager.h; path = Core/SDImageLoadersManager.h; sourceTree = "<group>"; };
@ -872,6 +878,8 @@
325312C7200F09910046BF1E /* SDWebImageTransition.m */,
32C0FDDF2013426C001B8F2D /* SDWebImageIndicator.h */,
32C0FDE02013426C001B8F2D /* SDWebImageIndicator.m */,
321117A7296573680001FC2C /* SDCallbackQueue.h */,
321117A8296573680001FC2C /* SDCallbackQueue.m */,
);
name = Utils;
sourceTree = "<group>";
@ -957,6 +965,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 +1214,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 */,

View File

@ -0,0 +1,54 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
/// SDCallbackPolicy controls how we execute the block on the queue, like whether to use `dispatch_async/dispatch_sync`, check if current queue match target queue, or just invoke without any context.
typedef NS_ENUM(NSUInteger, SDCallbackPolicy) {
/// When the current queue is equal to callback queue, sync/async will just invoke `block` directly without dispatch. Else it use `dispatch_async`/`dispatch_sync` to dispatch block on queue. This is useful for UIKit rendering to ensure all blocks executed in the same runloop
SDCallbackPolicySafeExecute = 0,
/// Follow async/sync using the correspond `dispatch_async`/`dispatch_sync` to dispatch block on queue
SDCallbackPolicyDispatch = 1,
/// Ignore any async/sync and just directly invoke `block` in current queue (without `dispatch_async`/`dispatch_sync`)
SDCallbackPolicyInvoke = 2
};
/// SDCallbackQueue is a wrapper used to control how the completionBlock should perform on queues, used by our `Cache`/`Manager`/`Loader`.
/// Useful when you call SDWebImage in non-main queue and want to avoid it callback into main queue, which may cause issue.
@interface SDCallbackQueue : NSObject
/// The shared main queue. This is the default value, has the same effect when passing `nil` to `SDWebImageContextCallbackQueue`
@property (nonnull, class, readonly) SDCallbackQueue *mainQueue;
/// The caller current queue. Using `dispatch_get_current_queue`. This is not a dynamic value and only keep the first call time queue.
@property (nonnull, class, readonly) SDCallbackQueue *currentQueue;
/// The global concurrent queue (user-initiated QoS). Using `dispatch_get_global_queue`.
@property (nonnull, class, readonly) SDCallbackQueue *globalQueue;
/// The current queue's callback policy, defaults to `SDCallbackPolicySafeExecute`, which behaves like the old macro `dispatch_main_async_safe`
@property (assign, readwrite) SDCallbackPolicy policy;
- (nonnull instancetype)init NS_UNAVAILABLE;
+ (nonnull instancetype)new NS_UNAVAILABLE;
/// Create the callback queue with a GCD queue
/// - Parameter queue: The GCD queue, should not be NULL
- (nonnull instancetype)initWithDispatchQueue:(nonnull dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER;
#pragma mark - Execution Entry
/// Submits a block for execution and returns after that block finishes executing.
/// - Parameter block: The block that contains the work to perform.
- (void)sync:(nonnull NS_NOESCAPE dispatch_block_t)block;
/// Schedules a block asynchronously for execution.
/// - Parameter block: The block that contains the work to perform.
- (void)async:(nonnull NS_NOESCAPE dispatch_block_t)block;
@end

View File

@ -0,0 +1,110 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDCallbackQueue.h"
@interface SDCallbackQueue ()
@property (nonatomic, strong, nonnull) dispatch_queue_t queue;
@end
static void * SDCallbackQueueKey = &SDCallbackQueueKey;
static void SDReleaseBlock(void *context) {
CFRelease(context);
}
static void inline SDSafeExecute(dispatch_queue_t _Nonnull queue, dispatch_block_t _Nonnull block, BOOL async) {
// Special handle for main queue label only (custom queue can have the same label)
const char *label = dispatch_queue_get_label(queue);
if (label && label == dispatch_queue_get_label(dispatch_get_main_queue())) {
const char *currentLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
if (label == currentLabel) {
block();
return;
}
}
// Check specific to detect queue equal
void *specific = dispatch_queue_get_specific(queue, SDCallbackQueueKey);
void *currentSpecific = dispatch_get_specific(SDCallbackQueueKey);
if (specific && currentSpecific && CFGetTypeID(specific) == CFUUIDGetTypeID() && CFGetTypeID(currentSpecific) == CFUUIDGetTypeID() && CFEqual(specific, currentSpecific)) {
block();
} else {
if (async) {
dispatch_async(queue, block);
} else {
dispatch_sync(queue, block);
}
}
}
@implementation SDCallbackQueue
- (instancetype)initWithDispatchQueue:(dispatch_queue_t)queue {
self = [super init];
if (self) {
NSCParameterAssert(queue);
CFUUIDRef UUID = CFUUIDCreate(kCFAllocatorDefault);
dispatch_queue_set_specific(queue, SDCallbackQueueKey, (void *)UUID, SDReleaseBlock);
_queue = queue;
}
return self;
}
+ (SDCallbackQueue *)mainQueue {
static dispatch_once_t onceToken;
static SDCallbackQueue *queue;
dispatch_once(&onceToken, ^{
queue = [[SDCallbackQueue alloc] initWithDispatchQueue:dispatch_get_main_queue()];
});
return queue;
}
+ (SDCallbackQueue *)currentQueue {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
SDCallbackQueue *queue = [[SDCallbackQueue alloc] initWithDispatchQueue:dispatch_get_current_queue()];
#pragma clang diagnostic pop
return queue;
}
+ (SDCallbackQueue *)globalQueue {
SDCallbackQueue *queue = [[SDCallbackQueue alloc] initWithDispatchQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)];
return queue;
}
- (void)sync:(nonnull NS_NOESCAPE dispatch_block_t)block {
switch (self.policy) {
case SDCallbackPolicySafeExecute:
SDSafeExecute(self.queue, block, NO);
break;
case SDCallbackPolicyDispatch:
dispatch_sync(self.queue, block);
break;
case SDCallbackPolicyInvoke:
block();
break;
}
}
- (void)async:(nonnull NS_NOESCAPE dispatch_block_t)block {
switch (self.policy) {
case SDCallbackPolicySafeExecute:
SDSafeExecute(self.queue, block, YES);
break;
case SDCallbackPolicyDispatch:
dispatch_async(self.queue, block);
break;
case SDCallbackPolicyInvoke:
block();
break;
}
}
@end

View File

@ -225,6 +225,28 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/**
* Asynchronously store an image into memory and disk cache at the given key.
*
* @param image The image to store
* @param imageData The image data as returned by the server, this representation will be used for disk storage
* instead of converting the given image object into a storable/compressed image format in order
* to save quality and CPU
* @param key The unique image cache key, usually it's image absolute URL
* @param options A mask to specify options to use for this store
* @param context The context options to use. Pass `.callbackQueue` to control callback queue
* @param cacheType The image store op cache type
* @param completionBlock A block executed after the operation is finished
* @note If no image data is provided and encode to disk, we will try to detect the image format (using either `sd_imageFormat` or `SDAnimatedImage` protocol method) and animation status, to choose the best matched format, including GIF, JPEG or PNG.
*/
- (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;
/**
* Synchronously store an image into memory cache at the given key.
*

View File

@ -14,12 +14,14 @@
#import "UIImage+MemoryCacheCost.h"
#import "UIImage+Metadata.h"
#import "UIImage+ExtendedCacheData.h"
#import "SDCallbackQueue.h"
@interface SDImageCacheToken ()
@property (nonatomic, strong, nullable, readwrite) NSString *key;
@property (nonatomic, assign, getter=isCancelled) BOOL cancelled;
@property (nonatomic, copy, nullable) SDImageCacheQueryCompletionBlock doneBlock;
@property (nonatomic, strong, nullable) SDCallbackQueue *callbackQueue;
@end
@ -43,9 +45,9 @@
SDImageCacheQueryCompletionBlock doneBlock = self.doneBlock;
self.doneBlock = nil;
if (doneBlock) {
dispatch_main_async_safe(^{
[(self.callbackQueue ?: SDCallbackQueue.mainQueue) async:^{
doneBlock(nil, nil, SDImageCacheTypeNone);
});
}];
}
}
}
@ -197,20 +199,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 +220,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 +236,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 +250,7 @@ static NSString * _defaultDiskCacheDirectory;
}
return;
}
SDCallbackQueue *queue = context[SDWebImageContextCallbackQueue];
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
@ -271,9 +277,9 @@ static NSString * _defaultDiskCacheDirectory;
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
[(queue ?: SDCallbackQueue.mainQueue) async:^{
completionBlock();
});
}];
}
});
}
@ -602,8 +608,10 @@ static NSString * _defaultDiskCacheDirectory;
}
// Second check the disk cache...
SDCallbackQueue *queue = context[SDWebImageContextCallbackQueue];
SDImageCacheToken *operation = [[SDImageCacheToken alloc] initWithDoneBlock:doneBlock];
operation.key = key;
operation.callbackQueue = queue;
// Check whether we need to synchronously query disk
// 1. in-memory cache hit & memoryDataSync
// 2. in-memory cache miss & diskDataSync
@ -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 {

View File

@ -81,22 +81,23 @@ FOUNDATION_EXPORT void SDSetDecodeOptionsToContext(SDWebImageMutableContext * _N
@param key The image cache key
@param options A mask to specify options to use for this query
@param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
@param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. Pass `.callbackQueue` to control callback queue
@param completionBlock The completion block. Will not get called if the operation is cancelled
@return The operation for this query
*/
- (nullable id<SDWebImageOperation>)queryImageForKey:(nullable NSString *)key
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock;
completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock API_DEPRECATED_WITH_REPLACEMENT("queryImageForKey:options:context:cacheType:completion:", 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));
@optional
/**
Query the cached image from image cache for given key. The operation can be used to cancel the query.
If image is cached in memory, completion is called synchronously, else asynchronously and depends on the options arg (See `SDWebImageQueryDiskSync`)
@param key The image cache key
@param options A mask to specify options to use for this query
@param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
@param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. Pass `.callbackQueue` to control callback queue
@param cacheType Specify where to query the cache from. By default we use `.all`, which means both memory cache and disk cache. You can choose to query memory only or disk only as well. Pass `.none` is invalid and callback with nil immediately.
@param completionBlock The completion block. Will not get called if the operation is cancelled
@return The operation for this query
@ -107,6 +108,7 @@ FOUNDATION_EXPORT void SDSetDecodeOptionsToContext(SDWebImageMutableContext * _N
cacheType:(SDImageCacheType)cacheType
completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock;
@required
/**
Store the image into image cache for the given key. If cache type is memory only, completion is called synchronously, else asynchronously.
@ -120,8 +122,29 @@ FOUNDATION_EXPORT void SDSetDecodeOptionsToContext(SDWebImageMutableContext * _N
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
cacheType:(SDImageCacheType)cacheType
completion:(nullable SDWebImageNoParamsBlock)completionBlock API_DEPRECATED_WITH_REPLACEMENT("storeImage:imageData:forKey:options:context:cacheType:completion:", 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));;
@optional
/**
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 `.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;
@required
/**
Remove the image from image cache for the given key. If cache type is memory only, completion is called synchronously, else asynchronously.

View File

@ -130,6 +130,10 @@
}
- (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:imageData forKey:key options:0 context:nil cacheType:cacheType completion:completionBlock];
}
- (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock {
if (!key) {
return;
}
@ -138,28 +142,28 @@
if (count == 0) {
return;
} else if (count == 1) {
[caches.firstObject storeImage:image imageData:imageData forKey:key cacheType:cacheType completion:completionBlock];
[caches.firstObject storeImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:completionBlock];
return;
}
switch (self.storeOperationPolicy) {
case SDImageCachesManagerOperationPolicyHighestOnly: {
id<SDImageCache> cache = caches.lastObject;
[cache storeImage:image imageData:imageData forKey:key cacheType:cacheType completion:completionBlock];
[cache storeImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyLowestOnly: {
id<SDImageCache> cache = caches.firstObject;
[cache storeImage:image imageData:imageData forKey:key cacheType:cacheType completion:completionBlock];
[cache storeImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyConcurrent: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
[self concurrentStoreImage:image imageData:imageData forKey:key cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
[self concurrentStoreImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
}
break;
case SDImageCachesManagerOperationPolicySerial: {
[self serialStoreImage:image imageData:imageData forKey:key cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator];
[self serialStoreImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator];
}
break;
default:
@ -315,11 +319,11 @@
}
}
- (void)concurrentStoreImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
- (void)concurrentStoreImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
NSParameterAssert(enumerator);
NSParameterAssert(operation);
for (id<SDImageCache> cache in enumerator) {
[cache storeImage:image imageData:imageData forKey:key cacheType:cacheType completion:^{
[cache storeImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:^{
if (operation.isCancelled) {
// Cancelled
return;
@ -462,7 +466,7 @@
}];
}
- (void)serialStoreImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator {
- (void)serialStoreImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator {
NSParameterAssert(enumerator);
id<SDImageCache> cache = enumerator.nextObject;
if (!cache) {
@ -473,10 +477,10 @@
return;
}
@weakify(self);
[cache storeImage:image imageData:imageData forKey:key cacheType:cacheType completion:^{
[cache storeImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:^{
@strongify(self);
// Next
[self serialStoreImage:image imageData:imageData forKey:key cacheType:cacheType completion:completionBlock enumerator:enumerator];
[self serialStoreImage:image imageData:imageData forKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:enumerator];
}];
}

View File

@ -81,7 +81,7 @@ FOUNDATION_EXPORT void SDImageLoaderSetProgressiveCoder(id<SDWebImageOperation>
@param url The image URL to be loaded.
@return YES to continue download, NO to stop download.
*/
- (BOOL)canRequestImageForURL:(nullable NSURL *)url API_DEPRECATED("Use canRequestImageForURL:options:context: 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));
- (BOOL)canRequestImageForURL:(nullable NSURL *)url API_DEPRECATED_WITH_REPLACEMENT("canRequestImageForURL:options:context:", 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));
@optional
/**
@ -125,7 +125,7 @@ FOUNDATION_EXPORT void SDImageLoaderSetProgressiveCoder(id<SDWebImageOperation>
@return Whether to block this url or not. Return YES to mark this URL as failed.
*/
- (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url
error:(nonnull NSError *)error API_DEPRECATED("Use shouldBlockFailedURLWithURL:error:options:context: 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));
error:(nonnull NSError *)error API_DEPRECATED_WITH_REPLACEMENT("shouldBlockFailedURLWithURL:error:options:context:", 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));
@optional
/**

View File

@ -221,6 +221,15 @@ 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));
/**
A `SDCallbackQueue` instance which controls the `Cache`/`Manager`/`Loader`'s callback queue for their completionBlock.
This is useful for user who call these 3 components in non-main queue and want to avoid callback in main queue.
@note For UI callback (`sd_setImageWithURL`), we will still use main queue to dispatch, means if you specify a global queue, it will enqueue from the global queue to main queue.
@note This does not effect the components' working queue (for example, `Cache` still query disk on internal ioQueue, `Loader` still do network on URLSessionConfiguration.delegateQueue), change those config if you need.
Defaults to nil. Which means main queue.
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCallbackQueue;
/**
A id<SDImageCache> 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<SDImageCache>)

View File

@ -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";

View File

@ -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
@ -157,12 +158,7 @@
@synchronized (self) {
[self.callbackTokens removeObjectIdenticalTo:token];
}
SDWebImageDownloaderCompletedBlock completedBlock = ((SDWebImageDownloaderOperationToken *)token).completedBlock;
if (completedBlock) {
dispatch_main_async_safe(^{
completedBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}], YES);
});
}
[self callCompletionBlockWithToken:token image:nil imageData:nil error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] finished:YES];
}
return shouldCancel;
}
@ -689,9 +685,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<SDWebImageDownloaderOperationToken *> *tokens;
@synchronized (self) {
tokens = [self.callbackTokens copy];
@ -699,9 +695,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) async:^{
completedBlock(image, imageData, error, finished);
});
}];
}
}
}
@ -713,9 +710,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) async:^{
completedBlock(image, imageData, error, finished);
});
}];
}
}

View File

@ -13,6 +13,7 @@
#import "SDAssociatedObject.h"
#import "SDWebImageError.h"
#import "SDInternalMacros.h"
#import "SDCallbackQueue.h"
static id<SDImageCache> _defaultImageCache;
static id<SDImageLoader> _defaultImageLoader;
@ -213,11 +214,14 @@ static id<SDImageLoader> _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<SDImageLoader> _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<SDImageLoader> _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<SDImageLoader> _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<SDImageLoader> _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<SDImageLoader> _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<SDImageLoader> _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];
}
}
@ -538,7 +539,6 @@ static id<SDImageLoader> _defaultImageLoader;
imageCache = self.imageCache;
}
}
BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
// the original store image cache type
SDImageCacheType originalStoreCacheType = SDImageCacheTypeDisk;
if (context[SDWebImageContextOriginalStoreCacheType]) {
@ -560,7 +560,7 @@ static id<SDImageLoader> _defaultImageLoader;
@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:^{
[self storeImage:originalImage imageData:newOriginalData forKey:key options:options context:context imageCache:imageCache cacheType:originalStoreCacheType finished:finished 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];
}];
@ -568,7 +568,7 @@ static id<SDImageLoader> _defaultImageLoader;
});
} else {
// Store original image and data
[self storeImage:originalImage imageData:originalData forKey:key imageCache:imageCache cacheType:originalStoreCacheType finished:finished waitStoreCache:waitStoreCache completion:^{
[self storeImage:originalImage imageData:originalData forKey:key options:options context:context imageCache:imageCache cacheType:originalStoreCacheType finished:finished 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];
}];
@ -590,7 +590,6 @@ static id<SDImageLoader> _defaultImageLoader;
if (!imageCache) {
imageCache = self.imageCache;
}
BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
// the target image store cache type
SDImageCacheType storeCacheType = SDImageCacheTypeAll;
if (context[SDWebImageContextStoreCacheType]) {
@ -604,14 +603,14 @@ static id<SDImageLoader> _defaultImageLoader;
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];
[self storeImage:image imageData:newData forKey:key options:options context:context imageCache:imageCache cacheType:storeCacheType finished:finished completion:^{
[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 storeImage:image imageData:data forKey:key options:options context:context imageCache:imageCache cacheType:storeCacheType finished:finished completion:^{
[self callCompletionBlockForOperation:operation completion:completedBlock image:image data:data error:nil cacheType:cacheType finished:finished queue:context[SDWebImageContextCallbackQueue] url:url];
}];
}
}
@ -630,11 +629,13 @@ static id<SDImageLoader> _defaultImageLoader;
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)data
forKey:(nullable NSString *)key
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
imageCache:(nonnull id<SDImageCache>)imageCache
cacheType:(SDImageCacheType)cacheType
finished:(BOOL)finished
waitStoreCache:(BOOL)waitStoreCache
completion:(nullable SDWebImageNoParamsBlock)completion {
BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
// Ignore progressive data cache
if (!finished) {
if (completion) {
@ -643,13 +644,23 @@ static id<SDImageLoader> _defaultImageLoader;
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) {
if (completion) {
completion();
if ([imageCache respondsToSelector:@selector(storeImage:imageData:forKey:options:context:cacheType:completion:)]) {
[imageCache storeImage:image imageData:data forKey:key options:options context:context cacheType:cacheType completion:^{
if (waitStoreCache) {
if (completion) {
completion();
}
}
}
}];
}];
} else {
[imageCache storeImage:image imageData:data forKey:key cacheType:cacheType completion:^{
if (waitStoreCache) {
if (completion) {
completion();
}
}
}];
}
if (!waitStoreCache) {
if (completion) {
completion();
@ -660,8 +671,9 @@ static id<SDImageLoader> _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 +683,12 @@ static id<SDImageLoader> _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) async:^{
completionBlock(image, data, error, cacheType, finished, url);
});
}];
}
}

View File

@ -76,20 +76,23 @@ typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls,
/**
* The options for prefetcher. Defaults to SDWebImageLowPriority.
* @deprecated Prefetcher is designed to be used shared and should not effect others. So in 5.15.0 we added API `prefetchURLs:options:context:`. If you want global control, try to use `SDWebImageOptionsProcessor` in manager level.
*/
@property (nonatomic, assign) SDWebImageOptions options;
@property (nonatomic, assign) SDWebImageOptions options API_DEPRECATED("Use individual prefetch options param 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));
/**
* The context for prefetcher. Defaults to nil.
* @deprecated Prefetcher is designed to be used shared and should not effect others. So in 5.15.0 we added API `prefetchURLs:options:context:`. If you want global control, try to use `SDWebImageOptionsProcessor` in `SDWebImageManager.optionsProcessor`.
*/
@property (nonatomic, copy, nullable) SDWebImageContext *context;
@property (nonatomic, copy, nullable) SDWebImageContext *context API_DEPRECATED("Use individual prefetch context param 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));
/**
* Queue options for prefetcher when call the progressBlock, completionBlock and delegate methods. Defaults to Main Queue.
* @note The call is asynchronously to avoid blocking target queue.
* @deprecated 5.15.0 introduce SDCallbackQueue, use that is preferred and has higher priority. The set/get to this property will translate to that instead.
* @note The call is asynchronously to avoid blocking target queue. (see SDCallbackPolicyDispatch)
* @note The delegate queue should be set before any prefetching start and may not be changed during prefetching to avoid thread-safe problem.
*/
@property (strong, nonatomic, nonnull) dispatch_queue_t delegateQueue;
@property (strong, nonatomic, nonnull) dispatch_queue_t delegateQueue API_DEPRECATED("Use SDWebImageContextCallbackQueue context param instead, see SDCallbackQueue", macos(10.10, 10.10), ios(8.0, 8.0), tvos(9.0, 9.0), watchos(2.0, 2.0));
/**
* The delegate for the prefetcher. Defaults to nil.
@ -134,6 +137,28 @@ typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls,
progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock;
/**
* Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching. It based on the image manager so the image may from the cache and network according to the `options` property.
* Prefetching is separate to each other, which means the progressBlock and completionBlock you provide is bind to the prefetching for the list of urls.
* Attention that call this will not cancel previous fetched urls. You should keep the token return by this to cancel or cancel all the prefetch.
*
* @param urls list of URLs to prefetch
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
* @param progressBlock block to be called when progress updates;
* first parameter is the number of completed (successful or not) requests,
* second parameter is the total number of images originally requested to be prefetched
* @param completionBlock block to be called when the current prefetching is completed
* first param is the number of completed (successful or not) requests,
* second parameter is the number of skipped requests
* @return the token to cancel the current prefetching.
*/
- (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray<NSURL *> *)urls
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock;
/**
* Remove and cancel all the prefeching for the prefetcher.
*/

View File

@ -8,9 +8,16 @@
#import "SDWebImagePrefetcher.h"
#import "SDAsyncBlockOperation.h"
#import "SDCallbackQueue.h"
#import "SDInternalMacros.h"
#import <stdatomic.h>
@interface SDCallbackQueue ()
@property (nonatomic, strong, nonnull) dispatch_queue_t queue;
@end
@interface SDWebImagePrefetchToken () {
@public
// Though current implementation, `SDWebImageManager` completion block is always on main queue. But however, there is no guarantee in docs. And we may introduce config to specify custom queue in the future.
@ -30,6 +37,8 @@
@property (nonatomic, strong) NSPointerArray *loadOperations;
@property (nonatomic, strong) NSPointerArray *prefetchOperations;
@property (nonatomic, weak) SDWebImagePrefetcher *prefetcher;
@property (nonatomic, assign) SDWebImageOptions options;
@property (nonatomic, copy, nullable) SDWebImageContext *context;
@property (nonatomic, copy, nullable) SDWebImagePrefetcherCompletionBlock completionBlock;
@property (nonatomic, copy, nullable) SDWebImagePrefetcherProgressBlock progressBlock;
@ -40,6 +49,7 @@
@property (strong, nonatomic, nonnull) SDWebImageManager *manager;
@property (strong, atomic, nonnull) NSMutableSet<SDWebImagePrefetchToken *> *runningTokens;
@property (strong, nonatomic, nonnull) NSOperationQueue *prefetchQueue;
@property (strong, nonatomic, nullable) SDCallbackQueue *callbackQueue;
@end
@ -63,7 +73,6 @@
_manager = manager;
_runningTokens = [NSMutableSet set];
_options = SDWebImageLowPriority;
_delegateQueue = dispatch_get_main_queue();
_prefetchQueue = [NSOperationQueue new];
self.maxConcurrentPrefetchCount = 3;
}
@ -78,6 +87,17 @@
return self.prefetchQueue.maxConcurrentOperationCount;
}
- (void)setDelegateQueue:(dispatch_queue_t)delegateQueue {
// Deprecate and translate to SDCallbackQueue
_callbackQueue = [[SDCallbackQueue alloc] initWithDispatchQueue:delegateQueue];
_callbackQueue.policy = SDCallbackPolicyDispatch;
}
- (dispatch_queue_t)delegateQueue {
// Deprecate and translate to SDCallbackQueue
return (_callbackQueue ?: SDCallbackQueue.mainQueue).queue;
}
#pragma mark - Prefetch
- (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray<NSURL *> *)urls {
return [self prefetchURLs:urls progress:nil completed:nil];
@ -86,6 +106,14 @@
- (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray<NSURL *> *)urls
progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock {
return [self prefetchURLs:urls options:self.options context:self.context progress:progressBlock completed:completionBlock];
}
- (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray<NSURL *> *)urls
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock {
if (!urls || urls.count == 0) {
if (completionBlock) {
completionBlock(0, 0);
@ -95,6 +123,8 @@
SDWebImagePrefetchToken *token = [SDWebImagePrefetchToken new];
token.prefetcher = self;
token.urls = urls;
token.options = options;
token.context = context;
token->_skippedCount = 0;
token->_finishedCount = 0;
token->_totalCount = token.urls.count;
@ -117,7 +147,7 @@
if (!self || asyncOperation.isCancelled) {
return;
}
id<SDWebImageOperation> operation = [self.manager loadImageWithURL:url options:self.options context:self.context progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
id<SDWebImageOperation> operation = [self.manager loadImageWithURL:url options:token.options context:token.context progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
@strongify(self);
if (!self) {
return;
@ -173,14 +203,18 @@
NSUInteger tokenTotalCount = [self tokenTotalCount];
NSUInteger finishedCount = atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed);
NSUInteger totalCount = token->_totalCount;
dispatch_async(self.delegateQueue, ^{
SDCallbackQueue *queue = token.context[SDWebImageContextCallbackQueue];
if (!queue) {
queue = self.callbackQueue;
}
[(queue ?: SDCallbackQueue.mainQueue) async:^{
if (shouldCallDelegate) {
[self.delegate imagePrefetcher:self didPrefetchURL:url finishedCount:tokenFinishedCount totalCount:tokenTotalCount];
}
if (token.progressBlock) {
token.progressBlock(finishedCount, totalCount);
}
});
}];
}
- (void)callCompletionBlockForToken:(SDWebImagePrefetchToken *)token {
@ -192,14 +226,18 @@
NSUInteger tokenSkippedCount = [self tokenSkippedCount];
NSUInteger finishedCount = atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed);
NSUInteger skippedCount = atomic_load_explicit(&(token->_skippedCount), memory_order_relaxed);
dispatch_async(self.delegateQueue, ^{
SDCallbackQueue *queue = token.context[SDWebImageContextCallbackQueue];
if (!queue) {
queue = self.callbackQueue;
}
[(queue ?: SDCallbackQueue.mainQueue) async:^{
if (shoulCallDelegate) {
[self.delegate imagePrefetcher:self didFinishWithTotalCount:tokenTotalCount skippedCount:tokenSkippedCount];
}
if (token.completionBlock) {
token.completionBlock(finishedCount, skippedCount);
}
});
}];
}
#pragma mark - Helper

View File

@ -0,0 +1 @@
../../Core/SDCallbackQueue.h

View File

@ -660,12 +660,18 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:@"Concurrent" diskCacheDirectory:@"/" config:config];
NSData *pngData = [NSData dataWithContentsOfFile:[self testPNGPath]];
[cache queryCacheOperationForKey:@"Key1" done:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
expect(data).beNil();
[expectation fulfill];
}];
[cache storeImageData:pngData forKey:@"Key1" completion:^{
[expectation fulfill];
// Added test case for custom queue
[SDCallbackQueue.globalQueue async:^{
SDWebImageContext *context = @{SDWebImageContextCallbackQueue : SDCallbackQueue.currentQueue};
expect(NSThread.isMainThread).beFalsy();
[cache queryCacheOperationForKey:@"Key1" options:0 context:context done:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
expect(data).beNil();
expect(NSThread.isMainThread).beFalsy();
[expectation fulfill];
}];
[cache storeImageData:pngData forKey:@"Key1" completion:^{
[expectation fulfill];
}];
}];
[self waitForExpectationsWithCommonTimeout];

View File

@ -154,6 +154,56 @@
expect(scaledImage.scale).equal(2);
}
- (void)testSDCallbackQueue {
XCTestExpectation *expectation1 = [self expectationWithDescription:@"SDCallbackQueue SafeExecute works"];
XCTestExpectation *expectation2 = [self expectationWithDescription:@"SDCallbackQueue Dispatch works"];
XCTestExpectation *expectation3 = [self expectationWithDescription:@"SDCallbackQueue Invoke works"];
dispatch_queue_t queue = dispatch_queue_create("testSDCallbackQueue", NULL);
SDCallbackQueue *callbackQueue = [[SDCallbackQueue alloc] initWithDispatchQueue:queue];
__block BOOL called = NO;
[callbackQueue sync:^{
called = YES;
}];
expect(called).beTruthy();
__block BOOL called1 = NO;
callbackQueue.policy = SDCallbackPolicySafeExecute;
dispatch_async(queue, ^{
// Should execute in sync
[callbackQueue async:^{
called1 = YES;
[expectation1 fulfill];
}];
expect(called1).beTruthy();
});
SDCallbackQueue *callbackQueue2 = [[SDCallbackQueue alloc] initWithDispatchQueue:queue];
__block BOOL called2 = NO;
callbackQueue2.policy = SDCallbackPolicyDispatch;
dispatch_async(queue, ^{
// Should execute in async
[callbackQueue2 async:^{
called2 = YES;
[expectation2 fulfill];
}];
expect(called2).beFalsy();
});
SDCallbackQueue *callbackQueue3 = [[SDCallbackQueue alloc] initWithDispatchQueue:queue];
__block BOOL called3 = NO;
callbackQueue3.policy = SDCallbackPolicyInvoke;
dispatch_async(queue, ^{
// Should execute in sync
[callbackQueue3 async:^{
called3 = YES;
[expectation3 fulfill];
}];
expect(called3).beTruthy();
});
[self waitForExpectationsWithCommonTimeout];
}
- (void)testInternalMacro {
@weakify(self);
@onExit {

View File

@ -265,6 +265,10 @@ static NSString * const SDWebImageTestDiskCacheExtendedAttributeName = @"com.hac
}
- (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:imageData forKey:key options:0 context:nil cacheType:cacheType completion:completionBlock];
}
- (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 {
switch (cacheType) {
case SDImageCacheTypeNone:
break;

View File

@ -18,6 +18,7 @@ FOUNDATION_EXPORT const unsigned char SDWebImageVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <SDWebImage/PublicHeader.h>
#import <SDWebImage/SDWebImageManager.h>
#import <SDWebImage/SDCallbackQueue.h>
#import <SDWebImage/SDWebImageCacheKeyFilter.h>
#import <SDWebImage/SDWebImageCacheSerializer.h>
#import <SDWebImage/SDImageCacheConfig.h>