Use dispatch_barrier to handle NSMutableDictionary thread unsafety instead of main thread dispatching
This commit is contained in:
parent
13210a6925
commit
570965f6cf
|
@ -20,6 +20,7 @@ NSString *const kCompletedCallbackKey = @"completed";
|
|||
|
||||
@property (strong, nonatomic) NSOperationQueue *downloadQueue;
|
||||
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
|
||||
@property (assign, nonatomic) dispatch_queue_t barrierQueue;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -65,6 +66,7 @@ NSString *const kCompletedCallbackKey = @"completed";
|
|||
_downloadQueue = NSOperationQueue.new;
|
||||
_downloadQueue.maxConcurrentOperationCount = 10;
|
||||
_URLCallbacks = NSMutableDictionary.new;
|
||||
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
@ -82,28 +84,9 @@ NSString *const kCompletedCallbackKey = @"completed";
|
|||
- (NSOperation *)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(void (^)(NSUInteger, long long))progressBlock completed:(void (^)(UIImage *, NSError *, BOOL))completedBlock
|
||||
{
|
||||
__block SDWebImageDownloaderOperation *operation;
|
||||
__weak SDWebImageDownloader *wself = self;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^ // NSDictionary isn't thread safe
|
||||
{
|
||||
BOOL performDownload = NO;
|
||||
|
||||
if (!self.URLCallbacks[url])
|
||||
{
|
||||
self.URLCallbacks[url] = NSMutableArray.new;
|
||||
performDownload = YES;
|
||||
}
|
||||
|
||||
// Handle single download of simultaneous download request for the same URL
|
||||
{
|
||||
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
|
||||
NSMutableDictionary *callbacks = NSMutableDictionary.new;
|
||||
if (progressBlock) callbacks[kProgressCallbackKey] = progressBlock;
|
||||
if (completedBlock) callbacks[kCompletedCallbackKey] = completedBlock;
|
||||
[callbacksForURL addObject:callbacks];
|
||||
self.URLCallbacks[url] = callbacksForURL;
|
||||
}
|
||||
|
||||
if (performDownload)
|
||||
[self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^
|
||||
{
|
||||
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest.alloc initWithURL:url cachePolicy:NSURLCacheStorageNotAllowed timeoutInterval:15];
|
||||
|
@ -112,41 +95,74 @@ NSString *const kCompletedCallbackKey = @"completed";
|
|||
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
|
||||
operation = [SDWebImageDownloaderOperation.alloc initWithRequest:request options:options progress:^(NSUInteger receivedSize, long long expectedSize)
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
|
||||
if (!wself) return;
|
||||
SDWebImageDownloader *sself = wself;
|
||||
NSArray *callbacksForURL = [sself removeAndReturnCallbacksForURL:url];
|
||||
for (NSDictionary *callbacks in callbacksForURL)
|
||||
{
|
||||
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
|
||||
if (callback) callback(receivedSize, expectedSize);
|
||||
}
|
||||
});
|
||||
}
|
||||
completed:^(UIImage *image, NSError *error, BOOL finished)
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
|
||||
[self.URLCallbacks removeObjectForKey:url];
|
||||
if (!wself) return;
|
||||
SDWebImageDownloader *sself = wself;
|
||||
NSArray *callbacksForURL = [sself removeAndReturnCallbacksForURL:url];
|
||||
for (NSDictionary *callbacks in callbacksForURL)
|
||||
{
|
||||
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
|
||||
if (callback) callback(image, error, finished);
|
||||
}
|
||||
});
|
||||
}
|
||||
cancelled:^
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
[self.URLCallbacks removeObjectForKey:url];
|
||||
});
|
||||
if (!wself) return;
|
||||
SDWebImageDownloader *sself = wself;
|
||||
[sself removeAndReturnCallbacksForURL:url];
|
||||
}];
|
||||
[self.downloadQueue addOperation:operation];
|
||||
}
|
||||
});
|
||||
}];
|
||||
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
- (void)addProgressCallback:(void (^)(NSUInteger, long long))progressBlock andCompletedBlock:(void (^)(UIImage *, NSError *, BOOL))completedBlock forURL:(NSURL *)url createCallback:(void (^)())createCallback
|
||||
{
|
||||
dispatch_barrier_async(self.barrierQueue, ^
|
||||
{
|
||||
BOOL first = NO;
|
||||
if (!self.URLCallbacks[url])
|
||||
{
|
||||
self.URLCallbacks[url] = NSMutableArray.new;
|
||||
first = YES;
|
||||
}
|
||||
|
||||
// Handle single download of simultaneous download request for the same URL
|
||||
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
|
||||
NSMutableDictionary *callbacks = NSMutableDictionary.new;
|
||||
if (progressBlock) callbacks[kProgressCallbackKey] = progressBlock;
|
||||
if (completedBlock) callbacks[kCompletedCallbackKey] = completedBlock;
|
||||
[callbacksForURL addObject:callbacks];
|
||||
self.URLCallbacks[url] = callbacksForURL;
|
||||
|
||||
if (first)
|
||||
{
|
||||
createCallback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (NSArray *)removeAndReturnCallbacksForURL:(NSURL *)url
|
||||
{
|
||||
__block NSArray *callbacksForURL;
|
||||
dispatch_barrier_sync(self.barrierQueue, ^
|
||||
{
|
||||
callbacksForURL = self.URLCallbacks[url];
|
||||
[self.URLCallbacks removeObjectForKey:url];
|
||||
});
|
||||
return callbacksForURL;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
Loading…
Reference in New Issue