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) NSOperationQueue *downloadQueue;
|
||||||
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
|
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
|
||||||
|
@property (assign, nonatomic) dispatch_queue_t barrierQueue;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -65,6 +66,7 @@ NSString *const kCompletedCallbackKey = @"completed";
|
||||||
_downloadQueue = NSOperationQueue.new;
|
_downloadQueue = NSOperationQueue.new;
|
||||||
_downloadQueue.maxConcurrentOperationCount = 10;
|
_downloadQueue.maxConcurrentOperationCount = 10;
|
||||||
_URLCallbacks = NSMutableDictionary.new;
|
_URLCallbacks = NSMutableDictionary.new;
|
||||||
|
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
@ -82,71 +84,85 @@ NSString *const kCompletedCallbackKey = @"completed";
|
||||||
- (NSOperation *)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(void (^)(NSUInteger, long long))progressBlock completed:(void (^)(UIImage *, NSError *, BOOL))completedBlock
|
- (NSOperation *)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(void (^)(NSUInteger, long long))progressBlock completed:(void (^)(UIImage *, NSError *, BOOL))completedBlock
|
||||||
{
|
{
|
||||||
__block SDWebImageDownloaderOperation *operation;
|
__block SDWebImageDownloaderOperation *operation;
|
||||||
|
__weak SDWebImageDownloader *wself = self;
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^ // NSDictionary isn't thread safe
|
[self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^
|
||||||
{
|
{
|
||||||
BOOL performDownload = NO;
|
// 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];
|
||||||
if (!self.URLCallbacks[url])
|
request.HTTPShouldHandleCookies = NO;
|
||||||
|
request.HTTPShouldUsePipelining = YES;
|
||||||
|
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
|
||||||
|
operation = [SDWebImageDownloaderOperation.alloc initWithRequest:request options:options progress:^(NSUInteger receivedSize, long long expectedSize)
|
||||||
{
|
{
|
||||||
self.URLCallbacks[url] = NSMutableArray.new;
|
if (!wself) return;
|
||||||
performDownload = YES;
|
SDWebImageDownloader *sself = wself;
|
||||||
}
|
NSArray *callbacksForURL = [sself removeAndReturnCallbacksForURL:url];
|
||||||
|
for (NSDictionary *callbacks in callbacksForURL)
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
// 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];
|
|
||||||
request.HTTPShouldHandleCookies = NO;
|
|
||||||
request.HTTPShouldUsePipelining = YES;
|
|
||||||
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
|
|
||||||
operation = [SDWebImageDownloaderOperation.alloc initWithRequest:request options:options progress:^(NSUInteger receivedSize, long long expectedSize)
|
|
||||||
{
|
{
|
||||||
dispatch_async(dispatch_get_main_queue(), ^
|
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
|
||||||
{
|
if (callback) callback(receivedSize, expectedSize);
|
||||||
NSMutableArray *callbacksForURL = self.URLCallbacks[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];
|
|
||||||
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];
|
|
||||||
});
|
|
||||||
}];
|
|
||||||
[self.downloadQueue addOperation:operation];
|
|
||||||
}
|
}
|
||||||
});
|
completed:^(UIImage *image, NSError *error, BOOL finished)
|
||||||
|
{
|
||||||
|
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:^
|
||||||
|
{
|
||||||
|
if (!wself) return;
|
||||||
|
SDWebImageDownloader *sself = wself;
|
||||||
|
[sself removeAndReturnCallbacksForURL:url];
|
||||||
|
}];
|
||||||
|
[self.downloadQueue addOperation:operation];
|
||||||
|
}];
|
||||||
|
|
||||||
|
|
||||||
return 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
|
@end
|
||||||
|
|
Loading…
Reference in New Issue