From 95277bae45a641427a30287592c86bb45d276ebe Mon Sep 17 00:00:00 2001 From: "John C. Daub" Date: Fri, 5 Jul 2013 09:58:20 -0500 Subject: [PATCH 01/15] Break a retain cycle --- SDWebImage/SDWebImageDownloader.m | 1 + 1 file changed, 1 insertion(+) diff --git a/SDWebImage/SDWebImageDownloader.m b/SDWebImage/SDWebImageDownloader.m index 10cd983e3..a3411cb93 100644 --- a/SDWebImage/SDWebImageDownloader.m +++ b/SDWebImage/SDWebImageDownloader.m @@ -160,6 +160,7 @@ - (NSInteger)maxConcurrentDownloads [wself.lastAddedOperation addDependency:operation]; wself.lastAddedOperation = operation; } + operation = nil; // break retain cycle }]; return operation; From b54f53092526956bcf2356dd5c24a9f6b33ff1aa Mon Sep 17 00:00:00 2001 From: Tony Issakov Date: Sun, 7 Jul 2013 12:46:04 +0800 Subject: [PATCH 02/15] Update README.md Minor fix to callback arguments. NSData included now. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5fb444681..84077fe61 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ It's also possible to use the async image downloader independently: { // progression tracking code } - completed:^(UIImage *image, NSError *error, BOOL finished) + completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) { if (image && finished) { From 282e8179193a49867575cd658ae64883aedbce7b Mon Sep 17 00:00:00 2001 From: masarusanjp Date: Thu, 11 Jul 2013 17:29:28 +0900 Subject: [PATCH 03/15] Use an empty string to if str was NULL. --- SDWebImage/SDImageCache.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SDWebImage/SDImageCache.m b/SDWebImage/SDImageCache.m index 5fe3aae84..72a797aaf 100644 --- a/SDWebImage/SDImageCache.m +++ b/SDWebImage/SDImageCache.m @@ -112,6 +112,10 @@ - (NSString *)defaultCachePathForKey:(NSString *)key - (NSString *)cachedFileNameForKey:(NSString *)key { const char *str = [key UTF8String]; + if (str == NULL) + { + str = ""; + } unsigned char r[CC_MD5_DIGEST_LENGTH]; CC_MD5(str, (CC_LONG)strlen(str), r); NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", From 6faa7bfcb5b73a31920ae7addf852673401f74f4 Mon Sep 17 00:00:00 2001 From: Giuseppe Lanza Date: Tue, 30 Jul 2013 17:14:52 +0200 Subject: [PATCH 04/15] added multiple download of images for animationImages property of UIImageView --- SDWebImage/UIImageView+WebCache.h | 9 +++++ SDWebImage/UIImageView+WebCache.m | 55 +++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/SDWebImage/UIImageView+WebCache.h b/SDWebImage/UIImageView+WebCache.h index 333ae3e6e..952a1e9df 100644 --- a/SDWebImage/UIImageView+WebCache.h +++ b/SDWebImage/UIImageView+WebCache.h @@ -134,9 +134,18 @@ */ - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock; +/** + * Download an array of images and starts them in an animation loop + * + *@param arrayOfURLs An array of NSURL + */ +-(void)setAnimationImagesWithURLs:(NSArray *)arrayOfURLs; + /** * Cancel the current download */ - (void)cancelCurrentImageLoad; +- (void)cancelCurrentArrayLoad; + @end diff --git a/SDWebImage/UIImageView+WebCache.m b/SDWebImage/UIImageView+WebCache.m index 59e61a122..dd9bfcbb0 100644 --- a/SDWebImage/UIImageView+WebCache.m +++ b/SDWebImage/UIImageView+WebCache.m @@ -10,6 +10,7 @@ #import "objc/runtime.h" static char operationKey; +static char operationArrayKey; @implementation UIImageView (WebCache) @@ -82,6 +83,46 @@ - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder opt } } +-(void)setAnimationImagesWithURLs:(NSArray *)arrayOfURLs{ + [self cancelCurrentArrayLoad]; + __weak UIImageView *wself = self; + + NSMutableArray *operationsArray = [[NSMutableArray alloc] init]; + + for(NSURL *logoImageURL in arrayOfURLs){ + id operation = [SDWebImageManager.sharedManager downloadWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished){ + if (!wself) return; + void (^block)(void) = ^ + { + __strong UIImageView *sself = wself; + [sself stopAnimating]; + if (!sself) return; + if (image) + { + NSMutableArray *currentImages = [[sself animationImages] mutableCopy]; + if(!currentImages) currentImages = [[NSMutableArray alloc] init]; + [currentImages addObject:image]; + + sself.animationImages = currentImages; + [sself setNeedsLayout]; + } + [sself startAnimating]; + }; + if ([NSThread isMainThread]) + { + block(); + } + else + { + dispatch_sync(dispatch_get_main_queue(), block); + } + }]; + [operationsArray addObject:operation]; + } + + objc_setAssociatedObject(self, &operationArrayKey, [NSArray arrayWithArray:operationsArray], OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + - (void)cancelCurrentImageLoad { // Cancel in progress downloader from queue @@ -93,4 +134,18 @@ - (void)cancelCurrentImageLoad } } +- (void)cancelCurrentArrayLoad +{ + // Cancel in progress downloader from queue + NSArray *operations = objc_getAssociatedObject(self, &operationArrayKey); + for(id operation in operations){ + if (operation) + { + [operation cancel]; + } + } + objc_setAssociatedObject(self, &operationArrayKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + +} + @end From fb196ead9bd29341bb37d04e77b508d2e7bf90a6 Mon Sep 17 00:00:00 2001 From: Chang Luo Date: Mon, 29 Jul 2013 14:06:15 -0700 Subject: [PATCH 05/15] Added background clean disk to UIApplicationDidEnterBackgroundNotification #306 --- SDWebImage/SDImageCache.m | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/SDWebImage/SDImageCache.m b/SDWebImage/SDImageCache.m index 72a797aaf..525e127c5 100644 --- a/SDWebImage/SDImageCache.m +++ b/SDWebImage/SDImageCache.m @@ -71,6 +71,11 @@ - (id)initWithNamespace:(NSString *)ns selector:@selector(cleanDisk) name:UIApplicationWillTerminateNotification object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(backgroundCleanDisk) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; #endif } @@ -402,7 +407,29 @@ - (void)cleanDisk }); } --(unsigned long long)getSize +- (void)backgroundCleanDisk +{ + UIApplication *application = [UIApplication sharedApplication]; + __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^ + { + // Clean up any unfinished task business by marking where you + // stopped or ending the task outright. + [application endBackgroundTask:bgTask]; + bgTask = UIBackgroundTaskInvalid; + }]; + + // Start the long-running task and return immediately. + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ + { + // Do the work associated with the task, preferably in chunks. + [self cleanDisk]; + + [application endBackgroundTask:bgTask]; + bgTask = UIBackgroundTaskInvalid; + }); +} + +- (unsigned long long)getSize { unsigned long long size = 0; NSDirectoryEnumerator *fileEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:self.diskCachePath]; From f07a34da59b35c7169b1c15b9d3f27cd32ff2d8f Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Wed, 7 Aug 2013 20:53:34 -0700 Subject: [PATCH 06/15] Fix downloadImageWithURL:options:progress:completed returning nil (fix #464) --- SDWebImage/SDWebImageDownloader.m | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/SDWebImage/SDWebImageDownloader.m b/SDWebImage/SDWebImageDownloader.m index a3411cb93..7188f77e8 100644 --- a/SDWebImage/SDWebImageDownloader.m +++ b/SDWebImage/SDWebImageDownloader.m @@ -111,7 +111,7 @@ - (NSInteger)maxConcurrentDownloads - (id)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(void (^)(NSUInteger, long long))progressBlock completed:(void (^)(UIImage *, NSData *, NSError *, BOOL))completedBlock { - __block SDWebImageDownloaderOperation *operation; + __block SDWebImageDownloaderOperation *blockOperation; __weak SDWebImageDownloader *wself = self; [self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^ @@ -121,7 +121,7 @@ - (NSInteger)maxConcurrentDownloads request.HTTPShouldHandleCookies = NO; request.HTTPShouldUsePipelining = YES; request.allHTTPHeaderFields = wself.HTTPHeaders; - operation = [SDWebImageDownloaderOperation.alloc initWithRequest:request options:options progress:^(NSUInteger receivedSize, long long expectedSize) + blockOperation = [SDWebImageDownloaderOperation.alloc initWithRequest:request options:options progress:^(NSUInteger receivedSize, long long expectedSize) { if (!wself) return; SDWebImageDownloader *sself = wself; @@ -153,16 +153,17 @@ - (NSInteger)maxConcurrentDownloads SDWebImageDownloader *sself = wself; [sself removeCallbacksForURL:url]; }]; - [wself.downloadQueue addOperation:operation]; + [wself.downloadQueue addOperation:blockOperation]; if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) { // Emulate LIFO execution order by systematically adding new operations as last operation's dependency - [wself.lastAddedOperation addDependency:operation]; - wself.lastAddedOperation = operation; + [wself.lastAddedOperation addDependency:blockOperation]; + wself.lastAddedOperation = blockOperation; } - operation = nil; // break retain cycle }]; + id operation = blockOperation; + blockOperation = nil; // break retain cycle return operation; } From 3a6d9481c9dd5a0e6a8c586cf6fb7f21127cbe65 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Wed, 7 Aug 2013 21:07:59 -0700 Subject: [PATCH 07/15] Call handlers on the main thread synchronously to enhance responsivity (fix #462) --- SDWebImage/MKAnnotationView+WebCache.m | 12 ++--------- SDWebImage/SDImageCache.m | 4 ++-- SDWebImage/SDWebImageCompat.h | 10 +++++++++ SDWebImage/SDWebImageDownloaderOperation.m | 2 +- SDWebImage/SDWebImageManager.m | 2 +- SDWebImage/UIButton+WebCache.m | 24 ++++------------------ SDWebImage/UIImageView+WebCache.m | 24 ++++------------------ 7 files changed, 24 insertions(+), 54 deletions(-) diff --git a/SDWebImage/MKAnnotationView+WebCache.m b/SDWebImage/MKAnnotationView+WebCache.m index 4485fb2cf..82dc5b9e2 100644 --- a/SDWebImage/MKAnnotationView+WebCache.m +++ b/SDWebImage/MKAnnotationView+WebCache.m @@ -50,7 +50,7 @@ - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder opt id operation = [SDWebImageManager.sharedManager downloadWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) { if (!wself) return; - void (^block)(void) = ^ + dispatch_main_sync_safe(^ { __strong MKAnnotationView *sself = wself; if (!sself) return; @@ -62,15 +62,7 @@ - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder opt { completedBlock(image, error, cacheType); } - }; - if ([NSThread isMainThread]) - { - block(); - } - else - { - dispatch_sync(dispatch_get_main_queue(), block); - } + }); }]; objc_setAssociatedObject(self, &operationKey, operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } diff --git a/SDWebImage/SDImageCache.m b/SDWebImage/SDImageCache.m index 525e127c5..f2411a605 100644 --- a/SDWebImage/SDImageCache.m +++ b/SDWebImage/SDImageCache.m @@ -280,7 +280,7 @@ - (void)queryDiskCacheForKey:(NSString *)key done:(void (^)(UIImage *image, SDIm [self.memCache setObject:diskImage forKey:key cost:cost]; } - dispatch_async(dispatch_get_main_queue(), ^ + dispatch_main_sync_safe(^ { doneBlock(diskImage, SDImageCacheTypeDisk); }); @@ -479,7 +479,7 @@ - (void)calculateSizeWithCompletionBlock:(void (^)(NSUInteger fileCount, unsigne if (completionBlock) { - dispatch_async(dispatch_get_main_queue(), ^ + dispatch_main_sync_safe(^ { completionBlock(fileCount, totalSize); }); diff --git a/SDWebImage/SDWebImageCompat.h b/SDWebImage/SDWebImageCompat.h index d36c69111..673af06cb 100644 --- a/SDWebImage/SDWebImageCompat.h +++ b/SDWebImage/SDWebImageCompat.h @@ -42,3 +42,13 @@ #endif extern inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image); + +#define dispatch_main_sync_safe(block)\ + if ([NSThread isMainThread])\ + {\ + block();\ + }\ + else\ + {\ + dispatch_sync(dispatch_get_main_queue(), block);\ + } diff --git a/SDWebImage/SDWebImageDownloaderOperation.m b/SDWebImage/SDWebImageDownloaderOperation.m index df394ab7a..6c7650278 100644 --- a/SDWebImage/SDWebImageDownloaderOperation.m +++ b/SDWebImage/SDWebImageDownloaderOperation.m @@ -230,7 +230,7 @@ - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data UIImage *scaledImage = [self scaledImageForKey:self.request.URL.absoluteString image:image]; image = [UIImage decodedImageWithImage:scaledImage]; CGImageRelease(partialImageRef); - dispatch_async(dispatch_get_main_queue(), ^ + dispatch_main_sync_safe(^ { if (self.completedBlock) { diff --git a/SDWebImage/SDWebImageManager.m b/SDWebImage/SDWebImageManager.m index ebe945dcb..9c680d96d 100644 --- a/SDWebImage/SDWebImageManager.m +++ b/SDWebImage/SDWebImageManager.m @@ -163,7 +163,7 @@ - (NSString *)cacheKeyForURL:(NSURL *)url { UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; - dispatch_async(dispatch_get_main_queue(), ^ + dispatch_main_sync_safe(^ { completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished); }); diff --git a/SDWebImage/UIButton+WebCache.m b/SDWebImage/UIButton+WebCache.m index ba1734dc7..3b8420e9b 100644 --- a/SDWebImage/UIButton+WebCache.m +++ b/SDWebImage/UIButton+WebCache.m @@ -49,7 +49,7 @@ - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderI id operation = [SDWebImageManager.sharedManager downloadWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) { if (!wself) return; - void (^block)(void) = ^ + dispatch_main_sync_safe(^ { __strong UIButton *sself = wself; if (!sself) return; @@ -61,15 +61,7 @@ - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderI { completedBlock(image, error, cacheType); } - }; - if ([NSThread isMainThread]) - { - block(); - } - else - { - dispatch_sync(dispatch_get_main_queue(), block); - } + }); }]; objc_setAssociatedObject(self, &operationKey, operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @@ -112,7 +104,7 @@ - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state pl id operation = [SDWebImageManager.sharedManager downloadWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) { if (!wself) return; - void (^block)(void) = ^ + dispatch_main_sync_safe(^ { __strong UIButton *sself = wself; if (!sself) return; @@ -124,15 +116,7 @@ - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state pl { completedBlock(image, error, cacheType); } - }; - if ([NSThread isMainThread]) - { - block(); - } - else - { - dispatch_sync(dispatch_get_main_queue(), block); - } + }); }]; objc_setAssociatedObject(self, &operationKey, operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } diff --git a/SDWebImage/UIImageView+WebCache.m b/SDWebImage/UIImageView+WebCache.m index 747c23984..fa772236e 100644 --- a/SDWebImage/UIImageView+WebCache.m +++ b/SDWebImage/UIImageView+WebCache.m @@ -56,7 +56,7 @@ - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder opt id operation = [SDWebImageManager.sharedManager downloadWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) { if (!wself) return; - void (^block)(void) = ^ + dispatch_main_sync_safe(^ { __strong UIImageView *sself = wself; if (!sself) return; @@ -69,15 +69,7 @@ - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder opt { completedBlock(image, error, cacheType); } - }; - if ([NSThread isMainThread]) - { - block(); - } - else - { - dispatch_sync(dispatch_get_main_queue(), block); - } + }); }]; objc_setAssociatedObject(self, &operationKey, operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @@ -95,7 +87,7 @@ - (void)setAnimationImagesWithURLs:(NSArray *)arrayOfURLs id operation = [SDWebImageManager.sharedManager downloadWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) { if (!wself) return; - void (^block)(void) = ^ + dispatch_main_sync_safe(^ { __strong UIImageView *sself = wself; [sself stopAnimating]; @@ -112,15 +104,7 @@ - (void)setAnimationImagesWithURLs:(NSArray *)arrayOfURLs [sself setNeedsLayout]; } [sself startAnimating]; - }; - if ([NSThread isMainThread]) - { - block(); - } - else - { - dispatch_sync(dispatch_get_main_queue(), block); - } + }); }]; [operationsArray addObject:operation]; } From 1e63f33a854f61371a7f3bf543ad283b2324711f Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Wed, 7 Aug 2013 21:15:32 -0700 Subject: [PATCH 08/15] Always call the completedBlock from main thread (fix #397) This is not a good practice but doing otherwise leads to a lot of mistakes --- SDWebImage/SDWebImageManager.m | 41 +++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/SDWebImage/SDWebImageManager.m b/SDWebImage/SDWebImageManager.m index 9c680d96d..06a389a2c 100644 --- a/SDWebImage/SDWebImageManager.m +++ b/SDWebImage/SDWebImageManager.m @@ -93,8 +93,11 @@ - (NSString *)cacheKeyForURL:(NSURL *)url { if (completedBlock) { - NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]; - completedBlock(nil, error, SDImageCacheTypeNone, YES); + dispatch_main_sync_safe(^ + { + NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]; + completedBlock(nil, error, SDImageCacheTypeNone, YES); + }); } return operation; } @@ -113,9 +116,12 @@ - (NSString *)cacheKeyForURL:(NSURL *)url { if (image && options & SDWebImageRefreshCached) { - // If image was found in the cache bug SDWebImageRefreshCached is provided, notify about the cached image - // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. - completedBlock(image, nil, cacheType, YES); + dispatch_main_sync_safe(^ + { + // If image was found in the cache bug SDWebImageRefreshCached is provided, notify about the cached image + // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. + completedBlock(image, nil, cacheType, YES); + }); } // download if no image or requested to refresh anyway, and download allowed by delegate @@ -134,11 +140,17 @@ - (NSString *)cacheKeyForURL:(NSURL *)url { if (weakOperation.cancelled) { - completedBlock(nil, nil, SDImageCacheTypeNone, finished); + dispatch_main_sync_safe(^ + { + completedBlock(nil, nil, SDImageCacheTypeNone, finished); + }); } else if (error) { - completedBlock(nil, error, SDImageCacheTypeNone, finished); + dispatch_main_sync_safe(^ + { + completedBlock(nil, error, SDImageCacheTypeNone, finished); + }); if (error.code != NSURLErrorNotConnectedToInternet) { @@ -177,7 +189,10 @@ - (NSString *)cacheKeyForURL:(NSURL *)url } else { - completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished); + dispatch_main_sync_safe(^ + { + completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished); + }); if (downloadedImage && finished) { @@ -198,7 +213,10 @@ - (NSString *)cacheKeyForURL:(NSURL *)url } else if (image) { - completedBlock(image, nil, cacheType, YES); + dispatch_main_sync_safe(^ + { + completedBlock(image, nil, cacheType, YES); + }); @synchronized(self.runningOperations) { [self.runningOperations removeObject:operation]; @@ -207,7 +225,10 @@ - (NSString *)cacheKeyForURL:(NSURL *)url else { // Image not in cache and download disallowed by delegate - completedBlock(nil, nil, SDImageCacheTypeNone, YES); + dispatch_main_sync_safe(^ + { + completedBlock(nil, nil, SDImageCacheTypeNone, YES); + }); @synchronized(self.runningOperations) { [self.runningOperations removeObject:operation]; From 5dd219ffa73f42205e1ac3474be85a933be82de2 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Thu, 8 Aug 2013 12:44:54 -0700 Subject: [PATCH 09/15] Try to fix the thread block (#399) --- SDWebImage/SDWebImageDownloader.m | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/SDWebImage/SDWebImageDownloader.m b/SDWebImage/SDWebImageDownloader.m index 7188f77e8..10cd983e3 100644 --- a/SDWebImage/SDWebImageDownloader.m +++ b/SDWebImage/SDWebImageDownloader.m @@ -111,7 +111,7 @@ - (NSInteger)maxConcurrentDownloads - (id)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(void (^)(NSUInteger, long long))progressBlock completed:(void (^)(UIImage *, NSData *, NSError *, BOOL))completedBlock { - __block SDWebImageDownloaderOperation *blockOperation; + __block SDWebImageDownloaderOperation *operation; __weak SDWebImageDownloader *wself = self; [self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^ @@ -121,7 +121,7 @@ - (NSInteger)maxConcurrentDownloads request.HTTPShouldHandleCookies = NO; request.HTTPShouldUsePipelining = YES; request.allHTTPHeaderFields = wself.HTTPHeaders; - blockOperation = [SDWebImageDownloaderOperation.alloc initWithRequest:request options:options progress:^(NSUInteger receivedSize, long long expectedSize) + operation = [SDWebImageDownloaderOperation.alloc initWithRequest:request options:options progress:^(NSUInteger receivedSize, long long expectedSize) { if (!wself) return; SDWebImageDownloader *sself = wself; @@ -153,17 +153,15 @@ - (NSInteger)maxConcurrentDownloads SDWebImageDownloader *sself = wself; [sself removeCallbacksForURL:url]; }]; - [wself.downloadQueue addOperation:blockOperation]; + [wself.downloadQueue addOperation:operation]; if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) { // Emulate LIFO execution order by systematically adding new operations as last operation's dependency - [wself.lastAddedOperation addDependency:blockOperation]; - wself.lastAddedOperation = blockOperation; + [wself.lastAddedOperation addDependency:operation]; + wself.lastAddedOperation = operation; } }]; - id operation = blockOperation; - blockOperation = nil; // break retain cycle return operation; } From 5f12cabdacb844ba48ffc19929795c62eacc5665 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Fri, 9 Aug 2013 00:39:46 -0700 Subject: [PATCH 10/15] Fix unreleased operation (fix #466, fix #444) --- SDWebImage/SDWebImageManager.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDWebImage/SDWebImageManager.m b/SDWebImage/SDWebImageManager.m index 06a389a2c..724c63d93 100644 --- a/SDWebImage/SDWebImageManager.m +++ b/SDWebImage/SDWebImageManager.m @@ -136,7 +136,7 @@ - (NSString *)cacheKeyForURL:(NSURL *)url // ignore image read from NSURLCache if image if cached but force refreshing downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; } - __block id subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) + id subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { if (weakOperation.cancelled) { From 454074538253cfccd936286adca97c1e5635c16c Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Fri, 9 Aug 2013 15:08:05 -0700 Subject: [PATCH 11/15] Fix the dead thread bug on iOS 5 (fix #466) --- SDWebImage/SDWebImageDownloaderOperation.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SDWebImage/SDWebImageDownloaderOperation.m b/SDWebImage/SDWebImageDownloaderOperation.m index 6c7650278..b7890ebd1 100644 --- a/SDWebImage/SDWebImageDownloaderOperation.m +++ b/SDWebImage/SDWebImageDownloaderOperation.m @@ -71,7 +71,9 @@ - (void)start [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self]; // Make sure to run the runloop in our background thread so it can process downloaded data - CFRunLoopRun(); + // Note: we use a timeout to work around an issue with NSURLConnection cancel under iOS 5 + // not waking up the runloop, leading to dead threads (see https://github.com/rs/SDWebImage/issues/466) + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false); } else { From ffd9e02e1e3a7b7dd9a4436d3fb9854f97c8622c Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Sun, 11 Aug 2013 13:16:47 -0700 Subject: [PATCH 12/15] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84077fe61..864bb77e4 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ There are two ways to use this in your project: copy all the files into your pro ### Add the SDWebImage project to your project -- Download and unzip the last version of the framework from the [download page](https://github.com/rs/SDWebImage/wiki/Download-Compiled-Framework) +- Download and unzip the last version of the framework from the [download page](https://github.com/rs/SDWebImage/releases) - Right-click on the project navigator and select "Add Files to "Your Project": - In the dialog, select SDWebImage.framework: - Check the "Copy items into destination group's folder (if needed)" checkbox From fa987a18182a22d68da182505ce7d96034e8dd93 Mon Sep 17 00:00:00 2001 From: Matt Pennig Date: Sun, 11 Aug 2013 21:14:34 -0500 Subject: [PATCH 13/15] Canceling the SDWebImageOperation will also cancel the expensive disk cache query operation. --- SDWebImage/SDImageCache.h | 2 +- SDWebImage/SDImageCache.m | 17 +++++++++++++---- SDWebImage/SDWebImageManager.m | 20 +++++++++++++++++--- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/SDWebImage/SDImageCache.h b/SDWebImage/SDImageCache.h index afe07f47f..133af9dd6 100644 --- a/SDWebImage/SDImageCache.h +++ b/SDWebImage/SDImageCache.h @@ -98,7 +98,7 @@ typedef enum SDImageCacheType SDImageCacheType; * * @param key The unique key used to store the wanted image */ -- (void)queryDiskCacheForKey:(NSString *)key done:(void (^)(UIImage *image, SDImageCacheType cacheType))doneBlock; +- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(void (^)(UIImage *image, SDImageCacheType cacheType))doneBlock; /** * Query the memory cache synchronously. diff --git a/SDWebImage/SDImageCache.m b/SDWebImage/SDImageCache.m index f2411a605..0b86507e1 100644 --- a/SDWebImage/SDImageCache.m +++ b/SDWebImage/SDImageCache.m @@ -251,14 +251,16 @@ - (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image return SDScaledImageForKey(key, image); } -- (void)queryDiskCacheForKey:(NSString *)key done:(void (^)(UIImage *image, SDImageCacheType cacheType))doneBlock +- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(void (^)(UIImage *image, SDImageCacheType cacheType))doneBlock { - if (!doneBlock) return; + NSOperation *operation = NSOperation.new; + + if (!doneBlock) return nil; if (!key) { doneBlock(nil, SDImageCacheTypeNone); - return; + return nil; } // First check the in-memory cache... @@ -266,11 +268,16 @@ - (void)queryDiskCacheForKey:(NSString *)key done:(void (^)(UIImage *image, SDIm if (image) { doneBlock(image, SDImageCacheTypeMemory); - return; + return nil; } dispatch_async(self.ioQueue, ^ { + if (operation.isCancelled) + { + return; + } + @autoreleasepool { UIImage *diskImage = [self diskImageForKey:key]; @@ -286,6 +293,8 @@ - (void)queryDiskCacheForKey:(NSString *)key done:(void (^)(UIImage *image, SDIm }); } }); + + return operation; } - (void)removeImageForKey:(NSString *)key diff --git a/SDWebImage/SDWebImageManager.m b/SDWebImage/SDWebImageManager.m index 724c63d93..7e2413771 100644 --- a/SDWebImage/SDWebImageManager.m +++ b/SDWebImage/SDWebImageManager.m @@ -14,6 +14,7 @@ @interface SDWebImageCombinedOperation : NSObject @property (assign, nonatomic, getter = isCancelled) BOOL cancelled; @property (copy, nonatomic) void (^cancelBlock)(); +@property (strong, nonatomic) NSOperation *cacheOperation; @end @@ -108,9 +109,17 @@ - (NSString *)cacheKeyForURL:(NSURL *)url } NSString *key = [self cacheKeyForURL:url]; - [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) + operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) { - if (operation.isCancelled) return; + if (operation.isCancelled) + { + @synchronized(self.runningOperations) + { + [self.runningOperations removeObject:operation]; + } + + return; + } if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { @@ -138,7 +147,7 @@ - (NSString *)cacheKeyForURL:(NSURL *)url } id subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { - if (weakOperation.cancelled) + if (weakOperation.isCancelled) { dispatch_main_sync_safe(^ { @@ -272,6 +281,11 @@ - (void)setCancelBlock:(void (^)())cancelBlock - (void)cancel { self.cancelled = YES; + if (self.cacheOperation) + { + [self.cacheOperation cancel]; + self.cacheOperation = nil; + } if (self.cancelBlock) { self.cancelBlock(); From f356b86c2942eaf01e3808dbd40b9d77a0bde3ac Mon Sep 17 00:00:00 2001 From: Kevin Cador Date: Tue, 13 Aug 2013 10:40:46 +0200 Subject: [PATCH 14/15] The callbacksForURL: method of SDWebImageDownloader now returns a copied array. This should fix the crash in multithread environment such as: Collection <__NSArrayM: 0x226d9310> was mutated while being enumerated. --- SDWebImage/SDWebImageDownloader.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDWebImage/SDWebImageDownloader.m b/SDWebImage/SDWebImageDownloader.m index 10cd983e3..063056fe2 100644 --- a/SDWebImage/SDWebImageDownloader.m +++ b/SDWebImage/SDWebImageDownloader.m @@ -208,7 +208,7 @@ - (NSArray *)callbacksForURL:(NSURL *)url { callbacksForURL = self.URLCallbacks[url]; }); - return callbacksForURL; + return [callbacksForURL copy]; } - (void)removeCallbacksForURL:(NSURL *)url From 67a28d90ef833a7ea15a2bbc92171e5a467bc028 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Tue, 13 Aug 2013 10:22:25 -0700 Subject: [PATCH 15/15] Change version to 3.4 --- SDWebImage.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SDWebImage.podspec b/SDWebImage.podspec index 4fa18810d..64632c48e 100644 --- a/SDWebImage.podspec +++ b/SDWebImage.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |s| s.name = 'SDWebImage' - s.version = '3.3' + s.version = '3.4' s.platform = :ios, '5.0' s.license = 'MIT' s.summary = 'Asynchronous image downloader with cache support with an UIImageView category.' s.homepage = 'https://github.com/rs/SDWebImage' s.author = { 'Olivier Poitrey' => 'rs@dailymotion.com' } - s.source = { :git => 'https://github.com/rs/SDWebImage.git', :tag => '3.3' } + s.source = { :git => 'https://github.com/rs/SDWebImage.git', :tag => '3.4' } s.description = 'This library provides a category for UIImageView with support for remote ' \ 'images coming from the web. It provides an UIImageView category adding web ' \