Change the asyncSafe to the queue level configuration, introduce SDCallbackPolicy

This commit is contained in:
DreamPiggy 2023-01-09 17:24:13 +08:00
parent 67520b9f55
commit 5a4b4cf16d
5 changed files with 60 additions and 48 deletions

View File

@ -9,6 +9,16 @@
#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
@ -22,26 +32,21 @@
/// 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;
/// 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;
#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;
/// 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:(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;
/// 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:(nonnull NS_NOESCAPE dispatch_block_t)block;
@end

View File

@ -20,6 +20,27 @@ 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, 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(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 {
@ -56,44 +77,30 @@ static void SDReleaseBlock(void *context) {
}
- (void)sync:(nonnull NS_NOESCAPE dispatch_block_t)block {
dispatch_sync(self.queue, block);
}
- (void)syncSafe:(nonnull NS_NOESCAPE dispatch_block_t)block {
// 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);
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 {
dispatch_async(self.queue, block);
}
- (void)asyncSafe:(nonnull NS_NOESCAPE dispatch_block_t)block {
// 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);
switch (self.policy) {
case SDCallbackPolicySafeExecute:
SDSafeExecute(self.queue, block, YES);
break;
case SDCallbackPolicyDispatch:
dispatch_async(self.queue, block);
break;
case SDCallbackPolicyInvoke:
block();
break;
}
}

View File

@ -45,7 +45,7 @@
SDImageCacheQueryCompletionBlock doneBlock = self.doneBlock;
self.doneBlock = nil;
if (doneBlock) {
[(self.queue ?: SDCallbackQueue.mainQueue) asyncSafe:^{
[(self.queue ?: SDCallbackQueue.mainQueue) async:^{
doneBlock(nil, nil, SDImageCacheTypeNone);
}];
}

View File

@ -696,7 +696,7 @@ didReceiveResponse:(NSURLResponse *)response
SDWebImageDownloaderCompletedBlock completedBlock = token.completedBlock;
if (completedBlock) {
SDCallbackQueue *queue = self.context[SDWebImageContextCallbackQueue];
[(queue ?: SDCallbackQueue.mainQueue) asyncSafe:^{
[(queue ?: SDCallbackQueue.mainQueue) async:^{
completedBlock(image, imageData, error, finished);
}];
}
@ -711,7 +711,7 @@ didReceiveResponse:(NSURLResponse *)response
SDWebImageDownloaderCompletedBlock completedBlock = token.completedBlock;
if (completedBlock) {
SDCallbackQueue *queue = self.context[SDWebImageContextCallbackQueue];
[(queue ?: SDCallbackQueue.mainQueue) asyncSafe:^{
[(queue ?: SDCallbackQueue.mainQueue) async:^{
completedBlock(image, imageData, error, finished);
}];
}

View File

@ -676,7 +676,7 @@ static id<SDImageLoader> _defaultImageLoader;
queue:(nullable SDCallbackQueue *)queue
url:(nullable NSURL *)url {
if (completionBlock) {
[(queue ?: SDCallbackQueue.mainQueue) asyncSafe:^{
[(queue ?: SDCallbackQueue.mainQueue) async:^{
completionBlock(image, data, error, cacheType, finished, url);
}];
}