Implements the Callback queue dispatch

Fix some missing components
This commit is contained in:
DreamPiggy 2023-01-06 18:41:29 +08:00
parent 43ec4726e1
commit b5d712a378
6 changed files with 124 additions and 21 deletions

View File

@ -9,24 +9,39 @@
#import "SDWebImageDefine.h"
NS_ASSUME_NONNULL_BEGIN
/// 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;
@property (nonnull, class, readonly) SDCallbackQueue *callerQueue;
/// 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;
+ (SDCallbackQueue *)dispatchQueue:(dispatch_queue_t)queue;
/// 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;
- (void)sync:(SDWebImageNoParamsBlock)block;
/// Submits a block for execution and returns after that block finishes executing.
/// - Parameter block: The block that contains the work to perform.
- (void)sync:(nullable SDWebImageNoParamsBlock)block;
- (void)async:(SDWebImageNoParamsBlock)block;
/// Submits a block for execution and returns after that block finishes executing. When the current enqueued queue matching the callback queue, call the block immediately.
/// @warning This will not works when using `dispatch_set_target_queue` or recursive queue context.
/// - Parameter block: The block that contains the work to perform.
- (void)syncSafe:(nullable SDWebImageNoParamsBlock)block;
- (void)asyncSafe:(SDWebImageNoParamsBlock)block;
/// Schedules a block asynchronously for execution.
/// - Parameter block: The block that contains the work to perform.
- (void)async:(nullable SDWebImageNoParamsBlock)block;
/// Schedules a block asynchronously for execution. When the current enqueued queue matching the callback queue, call the block immediately.
/// @warning This will not works when using `dispatch_set_target_queue` or recursive queue context.
/// - Parameter block: The block that contains the work to perform.
- (void)asyncSafe:(nullable SDWebImageNoParamsBlock)block;
@end
NS_ASSUME_NONNULL_END

View File

@ -9,10 +9,96 @@
#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);
}
@implementation SDCallbackQueue
- (void)sync:(dispatch_block_t)block {
- (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:(SDWebImageNoParamsBlock)block {
if (!block) return;
dispatch_sync(self.queue, block);
}
- (void)syncSafe:(SDWebImageNoParamsBlock)block {
if (!block) return;
// Special handle for main queue, faster
const char *currentLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
if (currentLabel && currentLabel == dispatch_queue_get_label(dispatch_get_main_queue())) {
block();
return;
}
// Check specific to detect queue equal
void *specific = dispatch_queue_get_specific(self.queue, SDCallbackQueueKey);
void *currentSpecific = dispatch_get_specific(SDCallbackQueueKey);
if (specific && currentSpecific && CFGetTypeID(specific) == CFUUIDGetTypeID() && CFGetTypeID(currentSpecific) == CFUUIDGetTypeID() && CFEqual(specific, currentSpecific)) {
block();
} else {
dispatch_sync(self.queue, block);
}
}
- (void)async:(SDWebImageNoParamsBlock)block {
if (!block) return;
dispatch_async(self.queue, block);
}
- (void)asyncSafe:(SDWebImageNoParamsBlock)block {
if (!block) return;
// Special handle for main queue, faster
const char *currentLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
if (currentLabel && currentLabel == dispatch_queue_get_label(dispatch_get_main_queue())) {
block();
return;
}
// Check specific to detect queue equal
void *specific = dispatch_queue_get_specific(self.queue, SDCallbackQueueKey);
void *currentSpecific = dispatch_get_specific(SDCallbackQueueKey);
if (specific && currentSpecific && CFGetTypeID(specific) == CFUUIDGetTypeID() && CFGetTypeID(currentSpecific) == CFUUIDGetTypeID() && CFEqual(specific, currentSpecific)) {
block();
} else {
dispatch_async(self.queue, block);
}
}
@end

View File

@ -21,6 +21,7 @@
@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 *queue;
@end
@ -44,9 +45,9 @@
SDImageCacheQueryCompletionBlock doneBlock = self.doneBlock;
self.doneBlock = nil;
if (doneBlock) {
dispatch_main_async_safe(^{
[(self.queue ?: SDCallbackQueue.mainQueue) asyncSafe:^{
doneBlock(nil, nil, SDImageCacheTypeNone);
});
}];
}
}
}
@ -609,14 +610,15 @@ static NSString * _defaultDiskCacheDirectory;
}
// Second check the disk cache...
SDCallbackQueue *queue = context[SDWebImageContextCallbackQueue];
SDImageCacheToken *operation = [[SDImageCacheToken alloc] initWithDoneBlock:doneBlock];
operation.key = key;
operation.queue = queue;
// Check whether we need to synchronously query disk
// 1. in-memory cache hit & memoryDataSync
// 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) {

View File

@ -222,7 +222,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));
/**
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;

View File

@ -158,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;
}

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>