|
|
@@ -17,17 +17,19 @@ NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDown
|
|
|
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
|
|
|
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
|
|
|
|
|
|
+static NSString *const kProgressCallbackKey = @"progress";
|
|
|
+static NSString *const kCompletedCallbackKey = @"completed";
|
|
|
+
|
|
|
@interface SDWebImageDownloaderOperation () <NSURLConnectionDataDelegate>
|
|
|
|
|
|
-@property (copy, nonatomic) SDWebImageDownloaderProgressBlock progressBlock;
|
|
|
-@property (copy, nonatomic) SDWebImageDownloaderCompletedBlock completedBlock;
|
|
|
-@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
|
|
|
+@property (strong, nonatomic) NSMutableArray *callbackBlocks;
|
|
|
|
|
|
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
|
|
|
@property (assign, nonatomic, getter = isFinished) BOOL finished;
|
|
|
@property (strong, nonatomic) NSMutableData *imageData;
|
|
|
@property (strong, nonatomic) NSURLConnection *connection;
|
|
|
@property (strong, atomic) NSThread *thread;
|
|
|
+@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
|
|
|
|
|
|
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
|
|
|
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
|
|
|
@@ -45,26 +47,61 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
@synthesize finished = _finished;
|
|
|
|
|
|
- (id)initWithRequest:(NSURLRequest *)request
|
|
|
- options:(SDWebImageDownloaderOptions)options
|
|
|
- progress:(SDWebImageDownloaderProgressBlock)progressBlock
|
|
|
- completed:(SDWebImageDownloaderCompletedBlock)completedBlock
|
|
|
- cancelled:(SDWebImageNoParamsBlock)cancelBlock {
|
|
|
+ options:(SDWebImageDownloaderOptions)options {
|
|
|
if ((self = [super init])) {
|
|
|
_request = request;
|
|
|
_shouldDecompressImages = YES;
|
|
|
_shouldUseCredentialStorage = YES;
|
|
|
_options = options;
|
|
|
- _progressBlock = [progressBlock copy];
|
|
|
- _completedBlock = [completedBlock copy];
|
|
|
- _cancelBlock = [cancelBlock copy];
|
|
|
+ _callbackBlocks = [NSMutableArray new];
|
|
|
_executing = NO;
|
|
|
_finished = NO;
|
|
|
_expectedSize = 0;
|
|
|
responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called
|
|
|
+ _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
|
|
|
}
|
|
|
return self;
|
|
|
}
|
|
|
|
|
|
+- (void)dealloc {
|
|
|
+ SDDispatchQueueRelease(_barrierQueue);
|
|
|
+}
|
|
|
+
|
|
|
+- (id)addHandlersForProgress:(SDWebImageDownloaderProgressBlock)progressBlock
|
|
|
+ completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
|
|
|
+ NSMutableDictionary *callbacks = [NSMutableDictionary new];
|
|
|
+ if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
|
|
|
+ if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
|
|
|
+ dispatch_barrier_async(self.barrierQueue, ^{
|
|
|
+ [self.callbackBlocks addObject:callbacks];
|
|
|
+ });
|
|
|
+ return callbacks;
|
|
|
+}
|
|
|
+
|
|
|
+- (NSArray *)callbacksForKey:(NSString *)key {
|
|
|
+ __block NSMutableArray *callbacks = nil;
|
|
|
+ dispatch_sync(self.barrierQueue, ^{
|
|
|
+ // We need to remove [NSNull null] because there might not always be a progress block for each callback
|
|
|
+ callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
|
|
|
+ [callbacks removeObjectIdenticalTo:[NSNull null]];
|
|
|
+ });
|
|
|
+ return callbacks;
|
|
|
+}
|
|
|
+
|
|
|
+- (BOOL)cancel:(id)token {
|
|
|
+ __block BOOL shouldCancel = NO;
|
|
|
+ dispatch_barrier_sync(self.barrierQueue, ^{
|
|
|
+ [self.callbackBlocks removeObjectIdenticalTo:token];
|
|
|
+ if (self.callbackBlocks.count == 0) {
|
|
|
+ shouldCancel = YES;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (shouldCancel) {
|
|
|
+ [self cancel];
|
|
|
+ }
|
|
|
+ return shouldCancel;
|
|
|
+}
|
|
|
+
|
|
|
- (void)start {
|
|
|
@synchronized (self) {
|
|
|
if (self.isCancelled) {
|
|
|
@@ -100,8 +137,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
[self.connection start];
|
|
|
|
|
|
if (self.connection) {
|
|
|
- if (self.progressBlock) {
|
|
|
- self.progressBlock(0, NSURLResponseUnknownLength);
|
|
|
+ for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
|
|
|
+ progressBlock(0, NSURLResponseUnknownLength);
|
|
|
}
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
|
|
|
@@ -123,8 +160,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
- if (self.completedBlock) {
|
|
|
- self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
|
|
|
+ for (SDWebImageDownloaderCompletedBlock completedBlock in [self callbacksForKey:kCompletedCallbackKey]) {
|
|
|
+ completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -161,7 +198,6 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
- (void)cancelInternal {
|
|
|
if (self.isFinished) return;
|
|
|
[super cancel];
|
|
|
- if (self.cancelBlock) self.cancelBlock();
|
|
|
|
|
|
if (self.connection) {
|
|
|
[self.connection cancel];
|
|
|
@@ -185,9 +221,9 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
}
|
|
|
|
|
|
- (void)reset {
|
|
|
- self.cancelBlock = nil;
|
|
|
- self.completedBlock = nil;
|
|
|
- self.progressBlock = nil;
|
|
|
+ dispatch_barrier_async(self.barrierQueue, ^{
|
|
|
+ [self.callbackBlocks removeAllObjects];
|
|
|
+ });
|
|
|
self.connection = nil;
|
|
|
self.imageData = nil;
|
|
|
self.thread = nil;
|
|
|
@@ -217,8 +253,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) {
|
|
|
NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
|
|
|
self.expectedSize = expected;
|
|
|
- if (self.progressBlock) {
|
|
|
- self.progressBlock(0, expected);
|
|
|
+ for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
|
|
|
+ progressBlock(0, expected);
|
|
|
}
|
|
|
|
|
|
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
|
|
|
@@ -241,8 +277,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
|
|
|
});
|
|
|
|
|
|
- if (self.completedBlock) {
|
|
|
- self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
|
|
|
+ for (SDWebImageDownloaderCompletedBlock completedBlock in [self callbacksForKey:kCompletedCallbackKey]) {
|
|
|
+ completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
|
|
|
}
|
|
|
CFRunLoopStop(CFRunLoopGetCurrent());
|
|
|
[self done];
|
|
|
@@ -252,7 +288,7 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
|
|
|
[self.imageData appendData:data];
|
|
|
|
|
|
- if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {
|
|
|
+ if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
|
|
|
// The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
|
|
|
// Thanks to the author @Nyx0uf
|
|
|
|
|
|
@@ -319,8 +355,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
}
|
|
|
CGImageRelease(partialImageRef);
|
|
|
dispatch_main_sync_safe(^{
|
|
|
- if (self.completedBlock) {
|
|
|
- self.completedBlock(image, nil, nil, NO);
|
|
|
+ for (SDWebImageDownloaderCompletedBlock completedBlock in [self callbacksForKey:kCompletedCallbackKey]) {
|
|
|
+ completedBlock(image, nil, nil, NO);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
@@ -329,8 +365,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
CFRelease(imageSource);
|
|
|
}
|
|
|
|
|
|
- if (self.progressBlock) {
|
|
|
- self.progressBlock(self.imageData.length, self.expectedSize);
|
|
|
+ for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
|
|
|
+ progressBlock(self.imageData.length, self.expectedSize);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -362,7 +398,7 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
}
|
|
|
|
|
|
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
|
|
|
- SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
|
|
|
+ NSArray *completionBlocks = [[self callbacksForKey:kCompletedCallbackKey] copy];
|
|
|
@synchronized(self) {
|
|
|
CFRunLoopStop(CFRunLoopGetCurrent());
|
|
|
self.thread = nil;
|
|
|
@@ -376,10 +412,12 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) {
|
|
|
responseFromCached = NO;
|
|
|
}
|
|
|
-
|
|
|
- if (completionBlock) {
|
|
|
+
|
|
|
+ if (completionBlocks.count > 0) {
|
|
|
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
|
|
|
- completionBlock(nil, nil, nil, YES);
|
|
|
+ for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {
|
|
|
+ completionBlock(nil, nil, nil, YES);
|
|
|
+ }
|
|
|
} else if (self.imageData) {
|
|
|
UIImage *image = [UIImage sd_imageWithData:self.imageData];
|
|
|
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
|
|
|
@@ -391,17 +429,20 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
image = [UIImage decodedImageWithImage:image];
|
|
|
}
|
|
|
}
|
|
|
- if (CGSizeEqualToSize(image.size, CGSizeZero)) {
|
|
|
- completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
|
|
|
- }
|
|
|
- else {
|
|
|
- completionBlock(image, self.imageData, nil, YES);
|
|
|
+ for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {
|
|
|
+ if (CGSizeEqualToSize(image.size, CGSizeZero)) {
|
|
|
+ completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ completionBlock(image, self.imageData, nil, YES);
|
|
|
+ }
|
|
|
}
|
|
|
} else {
|
|
|
- completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
|
|
|
+ for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {
|
|
|
+ completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- self.completionBlock = nil;
|
|
|
[self done];
|
|
|
}
|
|
|
|
|
|
@@ -415,8 +456,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- if (self.completedBlock) {
|
|
|
- self.completedBlock(nil, nil, error, YES);
|
|
|
+ for (SDWebImageDownloaderCompletedBlock completedBlock in [self callbacksForKey:kCompletedCallbackKey]) {
|
|
|
+ completedBlock(nil, nil, error, YES);
|
|
|
}
|
|
|
self.completionBlock = nil;
|
|
|
[self done];
|