Skip to content

Commit c085d53

Browse files
authored
Merge pull request SDWebImage#3766 from dreampiggy/bugfix/uiview_webcache_sdcallbackqueue
Deprecate dispatch_main_async_safe, introduce two more policy `.safeAsyncMainThread` (for UIKit diffable data source) and `.safeAsyncMainQueue` (for most common cases)
2 parents 92a7ab9 + b4c741a commit c085d53

File tree

4 files changed

+79
-53
lines changed

4 files changed

+79
-53
lines changed

SDWebImage/Core/SDCallbackQueue.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,35 @@ typedef NS_ENUM(NSUInteger, SDCallbackPolicy) {
1616
/// Follow async/sync using the correspond `dispatch_async`/`dispatch_sync` to dispatch block on queue
1717
SDCallbackPolicyDispatch = 1,
1818
/// Ignore any async/sync and just directly invoke `block` in current queue (without `dispatch_async`/`dispatch_sync`)
19-
SDCallbackPolicyInvoke = 2
19+
SDCallbackPolicyInvoke = 2,
20+
/// Ensure callback in main queue (no gurantee on main thread). Do `dispatch_async` if the current queue is not main queue; else do invoke `block`. Never use `dispatch_sync`, suitable for general UI-related code
21+
SDCallbackPolicySafeAsyncMainQueue = 3,
22+
/// Ensure callback in main thread. Do `dispatch_async` if the `NSThread.isMainTrhead == true` ; else do invoke `block`. Never use `dispatch_sync`, suitable for special UI-related code
23+
SDCallbackPolicySafeAsyncMainThread = 4,
2024
};
2125

2226
/// SDCallbackQueue is a wrapper used to control how the completionBlock should perform on queues, used by our `Cache`/`Manager`/`Loader`.
2327
/// Useful when you call SDWebImage in non-main queue and want to avoid it callback into main queue, which may cause issue.
2428
@interface SDCallbackQueue : NSObject
2529

26-
/// The shared main queue. This is the default value, has the same effect when passing `nil` to `SDWebImageContextCallbackQueue`
30+
/// The main queue. This is the default value, has the same effect when passing `nil` to `SDWebImageContextCallbackQueue`
31+
/// The policy defaults to `SDCallbackPolicySafeAsyncMainQueue`
2732
@property (nonnull, class, readonly) SDCallbackQueue *mainQueue;
2833

2934
/// The caller current queue. Using `dispatch_get_current_queue`. This is not a dynamic value and only keep the first call time queue.
35+
/// The policy defaults to `SDCallbackPolicySafeExecute`
3036
@property (nonnull, class, readonly) SDCallbackQueue *currentQueue;
3137

3238
/// The global concurrent queue (user-initiated QoS). Using `dispatch_get_global_queue`.
39+
/// The policy defaults to `SDCallbackPolicySafeExecute`
3340
@property (nonnull, class, readonly) SDCallbackQueue *globalQueue;
3441

35-
/// The current queue's callback policy, defaults to `SDCallbackPolicySafeExecute`, which behaves like the old macro `dispatch_main_async_safe`
36-
@property (assign, readwrite) SDCallbackPolicy policy;
42+
/// The current queue's callback policy.
43+
@property (nonatomic, assign, readwrite) SDCallbackPolicy policy;
3744

3845
- (nonnull instancetype)init NS_UNAVAILABLE;
3946
+ (nonnull instancetype)new NS_UNAVAILABLE;
40-
/// Create the callback queue with a GCD queue
47+
/// Create the callback queue with a GCD queue. The policy defaults to `SDCallbackPolicySafeExecute`
4148
/// - Parameter queue: The GCD queue, should not be NULL
4249
- (nonnull instancetype)initWithDispatchQueue:(nonnull dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER;
4350

SDWebImage/Core/SDCallbackQueue.m

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,40 @@ @interface SDCallbackQueue ()
1515

1616
@end
1717

18-
static void * SDCallbackQueueKey = &SDCallbackQueueKey;
19-
static void SDReleaseBlock(void *context) {
20-
CFRelease(context);
21-
}
22-
23-
static void SDSafeExecute(SDCallbackQueue *callbackQueue, dispatch_block_t _Nonnull block, BOOL async) {
24-
// Extendc gcd queue's life cycle
25-
dispatch_queue_t queue = callbackQueue.queue;
26-
// Special handle for main queue label only (custom queue can have the same label)
27-
const char *label = dispatch_queue_get_label(queue);
28-
if (label && label == dispatch_queue_get_label(dispatch_get_main_queue())) {
18+
static inline void SDSafeMainQueueAsync(dispatch_block_t _Nonnull block) {
19+
if (NSThread.isMainThread) {
20+
// Match exists `dispatch_main_async_safe` behavior
2921
const char *currentLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
30-
if (label == currentLabel) {
22+
const char *mainLabel = dispatch_queue_get_label(dispatch_get_main_queue());
23+
if (mainLabel == currentLabel) {
3124
block();
3225
return;
3326
}
3427
}
35-
// Check specific to detect queue equal
36-
void *specific = dispatch_queue_get_specific(queue, SDCallbackQueueKey);
37-
if (specific && CFGetTypeID(specific) == CFUUIDGetTypeID()) {
38-
void *currentSpecific = dispatch_get_specific(SDCallbackQueueKey);
39-
if (currentSpecific && CFGetTypeID(currentSpecific) == CFUUIDGetTypeID() && CFEqual(specific, currentSpecific)) {
40-
block();
41-
return;
42-
}
28+
dispatch_async(dispatch_get_main_queue(), block);
29+
}
30+
31+
static inline void SDSafeMainThreadAsync(dispatch_block_t _Nonnull block) {
32+
if (NSThread.isMainThread) {
33+
block();
34+
} else {
35+
dispatch_async(dispatch_get_main_queue(), block);
36+
}
37+
}
38+
39+
static void SDSafeExecute(dispatch_queue_t queue, dispatch_block_t _Nonnull block, BOOL async) {
40+
#pragma clang diagnostic push
41+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
42+
dispatch_queue_t currentQueue = dispatch_get_current_queue();
43+
#pragma clang diagnostic pop
44+
if (queue == currentQueue) {
45+
block();
46+
return;
47+
}
48+
// Special handle for main queue only
49+
if (NSThread.isMainThread && queue == dispatch_get_main_queue()) {
50+
block();
51+
return;
4352
}
4453
if (async) {
4554
dispatch_async(queue, block);
@@ -54,19 +63,15 @@ - (instancetype)initWithDispatchQueue:(dispatch_queue_t)queue {
5463
self = [super init];
5564
if (self) {
5665
NSCParameterAssert(queue);
57-
CFUUIDRef UUID = CFUUIDCreate(kCFAllocatorDefault);
58-
dispatch_queue_set_specific(queue, SDCallbackQueueKey, (void *)UUID, SDReleaseBlock);
5966
_queue = queue;
67+
_policy = SDCallbackPolicySafeExecute;
6068
}
6169
return self;
6270
}
6371

6472
+ (SDCallbackQueue *)mainQueue {
65-
static dispatch_once_t onceToken;
66-
static SDCallbackQueue *queue;
67-
dispatch_once(&onceToken, ^{
68-
queue = [[SDCallbackQueue alloc] initWithDispatchQueue:dispatch_get_main_queue()];
69-
});
73+
SDCallbackQueue *queue = [[SDCallbackQueue alloc] initWithDispatchQueue:dispatch_get_main_queue()];
74+
queue->_policy = SDCallbackPolicySafeAsyncMainQueue;
7075
return queue;
7176
}
7277

@@ -86,33 +91,45 @@ + (SDCallbackQueue *)globalQueue {
8691
- (void)sync:(nonnull dispatch_block_t)block {
8792
switch (self.policy) {
8893
case SDCallbackPolicySafeExecute:
89-
SDSafeExecute(self, block, NO);
94+
SDSafeExecute(self.queue, block, NO);
9095
break;
9196
case SDCallbackPolicyDispatch:
9297
dispatch_sync(self.queue, block);
9398
break;
9499
case SDCallbackPolicyInvoke:
95100
block();
96101
break;
102+
case SDCallbackPolicySafeAsyncMainQueue:
103+
SDSafeMainQueueAsync(block);
104+
break;
105+
case SDCallbackPolicySafeAsyncMainThread:
106+
SDSafeMainThreadAsync(block);
107+
break;
97108
default:
98-
SDSafeExecute(self, block, NO);
109+
NSCAssert(NO, @"unexpected policy %tu", self.policy);
99110
break;
100111
}
101112
}
102113

103114
- (void)async:(nonnull dispatch_block_t)block {
104115
switch (self.policy) {
105116
case SDCallbackPolicySafeExecute:
106-
SDSafeExecute(self, block, YES);
117+
SDSafeExecute(self.queue, block, YES);
107118
break;
108119
case SDCallbackPolicyDispatch:
109120
dispatch_async(self.queue, block);
110121
break;
111122
case SDCallbackPolicyInvoke:
112123
block();
113124
break;
125+
case SDCallbackPolicySafeAsyncMainQueue:
126+
SDSafeMainQueueAsync(block);
127+
break;
128+
case SDCallbackPolicySafeAsyncMainThread:
129+
SDSafeMainThreadAsync(block);
130+
break;
114131
default:
115-
SDSafeExecute(self, block, YES);
132+
NSCAssert(NO, @"unexpected policy %tu", self.policy);
116133
break;
117134
}
118135
}

SDWebImage/Core/SDWebImageCompat.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,5 @@
9898
} else {\
9999
dispatch_async(dispatch_get_main_queue(), block);\
100100
}
101+
#pragma clang deprecated(dispatch_main_async_safe, "Use SDCallbackQueue instead")
101102
#endif

SDWebImage/Core/UIView+WebCache.m

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#import "SDInternalMacros.h"
1414
#import "SDWebImageTransitionInternal.h"
1515
#import "SDImageCache.h"
16+
#import "SDCallbackQueue.h"
1617

1718
const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
1819

@@ -108,7 +109,7 @@ - (void)setSd_imageProgress:(NSProgress *)sd_imageProgress {
108109
mutableContext[SDWebImageContextCustomManager] = nil;
109110
context = [mutableContext copy];
110111
}
111-
112+
SDCallbackQueue *queue = context[SDWebImageContextCallbackQueue];
112113
BOOL shouldUseWeakCache = NO;
113114
if ([manager.imageCache isKindOfClass:SDImageCache.class]) {
114115
shouldUseWeakCache = ((SDImageCache *)manager.imageCache).config.shouldUseWeakMemoryCache;
@@ -121,9 +122,9 @@ - (void)setSd_imageProgress:(NSProgress *)sd_imageProgress {
121122
// in the future the weak cache feature may be re-design or removed
122123
[((SDImageCache *)manager.imageCache) imageFromMemoryCacheForKey:key];
123124
}
124-
dispatch_main_async_safe(^{
125+
[(queue ?: SDCallbackQueue.mainQueue) async:^{
125126
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
126-
});
127+
}];
127128
}
128129

129130
id <SDWebImageOperation> operation = nil;
@@ -138,7 +139,7 @@ - (void)setSd_imageProgress:(NSProgress *)sd_imageProgress {
138139

139140
#if SD_UIKIT || SD_MAC
140141
// check and start image indicator
141-
[self sd_startImageIndicator];
142+
[self sd_startImageIndicatorWithQueue:queue];
142143
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
143144
#endif
144145

@@ -176,7 +177,7 @@ - (void)setSd_imageProgress:(NSProgress *)sd_imageProgress {
176177
#if SD_UIKIT || SD_MAC
177178
// check and stop image indicator
178179
if (finished) {
179-
[self sd_stopImageIndicator];
180+
[self sd_stopImageIndicatorWithQueue:queue];
180181
}
181182
#endif
182183

@@ -197,7 +198,7 @@ - (void)setSd_imageProgress:(NSProgress *)sd_imageProgress {
197198
// OR
198199
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
199200
if (shouldNotSetImage) {
200-
dispatch_main_async_safe(callCompletedBlockClosure);
201+
[(queue ?: SDCallbackQueue.mainQueue) async:callCompletedBlockClosure];
201202
return;
202203
}
203204

@@ -242,25 +243,25 @@ - (void)setSd_imageProgress:(NSProgress *)sd_imageProgress {
242243
transition = self.sd_imageTransition;
243244
}
244245
#endif
245-
dispatch_main_async_safe(^{
246+
[(queue ?: SDCallbackQueue.mainQueue) async:^{
246247
#if SD_UIKIT || SD_MAC
247248
[self sd_setImage:targetImage imageData:targetData options:options basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL callback:callCompletedBlockClosure];
248249
#else
249250
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
250251
callCompletedBlockClosure();
251252
#endif
252-
});
253+
}];
253254
}];
254255
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
255256
} else {
256257
#if SD_UIKIT || SD_MAC
257-
[self sd_stopImageIndicator];
258+
[self sd_stopImageIndicatorWithQueue:queue];
258259
#endif
259260
if (completedBlock) {
260-
dispatch_main_async_safe(^{
261+
[(queue ?: SDCallbackQueue.mainQueue) async:^{
261262
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
262263
completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
263-
});
264+
}];
264265
}
265266
}
266267

@@ -482,24 +483,24 @@ - (void)setSd_imageIndicator:(id<SDWebImageIndicator>)sd_imageIndicator {
482483
[self addSubview:view];
483484
}
484485

485-
- (void)sd_startImageIndicator {
486+
- (void)sd_startImageIndicatorWithQueue:(SDCallbackQueue *)queue {
486487
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
487488
if (!imageIndicator) {
488489
return;
489490
}
490-
dispatch_main_async_safe(^{
491+
[(queue ?: SDCallbackQueue.mainQueue) async:^{
491492
[imageIndicator startAnimatingIndicator];
492-
});
493+
}];
493494
}
494495

495-
- (void)sd_stopImageIndicator {
496+
- (void)sd_stopImageIndicatorWithQueue:(SDCallbackQueue *)queue {
496497
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
497498
if (!imageIndicator) {
498499
return;
499500
}
500-
dispatch_main_async_safe(^{
501+
[(queue ?: SDCallbackQueue.mainQueue) async:^{
501502
[imageIndicator stopAnimatingIndicator];
502-
});
503+
}];
503504
}
504505

505506
#endif

0 commit comments

Comments
 (0)