Authored by Olivier Poitrey

Ensure image isn't decoded twice if not necessary when SDWebImageRefreshCached flag is used #326

@@ -14,7 +14,16 @@ typedef enum @@ -14,7 +14,16 @@ typedef enum
14 { 14 {
15 SDWebImageDownloaderLowPriority = 1 << 0, 15 SDWebImageDownloaderLowPriority = 1 << 0,
16 SDWebImageDownloaderProgressiveDownload = 1 << 1, 16 SDWebImageDownloaderProgressiveDownload = 1 << 1,
17 - SDWebImageDownloaderEnableNSURLCache = 1 << 2 17 + /**
  18 + * By default, request prevent the of NSURLCache. With this flag, NSURLCache
  19 + * is used with default policies.
  20 + */
  21 + SDWebImageDownloaderUseNSURLCache = 1 << 2,
  22 + /**
  23 + * Call completion block with nil image/imageData if the image was read from NSURLCache
  24 + * (to be combined with `SDWebImageDownloaderUseNSURLCache`).
  25 + */
  26 + SDWebImageDownloaderIgnoreCachedResponse = 1 << 3
18 } SDWebImageDownloaderOptions; 27 } SDWebImageDownloaderOptions;
19 28
20 typedef enum 29 typedef enum
@@ -120,7 +120,7 @@ static NSString *const kCompletedCallbackKey = @"completed"; @@ -120,7 +120,7 @@ static NSString *const kCompletedCallbackKey = @"completed";
120 [self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^ 120 [self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^
121 { 121 {
122 // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise 122 // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
123 - NSMutableURLRequest *request = [NSMutableURLRequest.alloc initWithURL:url cachePolicy:(options & SDWebImageDownloaderEnableNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:15]; 123 + NSMutableURLRequest *request = [NSMutableURLRequest.alloc initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:15];
124 request.HTTPShouldHandleCookies = NO; 124 request.HTTPShouldHandleCookies = NO;
125 request.HTTPShouldUsePipelining = YES; 125 request.HTTPShouldUsePipelining = YES;
126 request.allHTTPHeaderFields = wself.HTTPHeaders; 126 request.allHTTPHeaderFields = wself.HTTPHeaders;
@@ -28,6 +28,7 @@ @@ -28,6 +28,7 @@
28 @implementation SDWebImageDownloaderOperation 28 @implementation SDWebImageDownloaderOperation
29 { 29 {
30 size_t width, height; 30 size_t width, height;
  31 + BOOL responseFromCached;
31 } 32 }
32 33
33 - (id)initWithRequest:(NSURLRequest *)request queue:(dispatch_queue_t)queue options:(SDWebImageDownloaderOptions)options progress:(void (^)(NSUInteger, long long))progressBlock completed:(void (^)(UIImage *, NSData *, NSError *, BOOL))completedBlock cancelled:(void (^)())cancelBlock 34 - (id)initWithRequest:(NSURLRequest *)request queue:(dispatch_queue_t)queue options:(SDWebImageDownloaderOptions)options progress:(void (^)(NSUInteger, long long))progressBlock completed:(void (^)(UIImage *, NSData *, NSError *, BOOL))completedBlock cancelled:(void (^)())cancelBlock
@@ -43,6 +44,7 @@ @@ -43,6 +44,7 @@
43 _executing = NO; 44 _executing = NO;
44 _finished = NO; 45 _finished = NO;
45 _expectedSize = 0; 46 _expectedSize = 0;
  47 + responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called
46 } 48 }
47 return self; 49 return self;
48 } 50 }
@@ -271,25 +273,35 @@ @@ -271,25 +273,35 @@
271 [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:nil]; 273 [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:nil];
272 274
273 SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock; 275 SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
  276 +
274 if (completionBlock) 277 if (completionBlock)
275 { 278 {
276 - dispatch_async(self.queue, ^ 279 + if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached)
  280 + {
  281 + completionBlock(nil, nil, nil, YES);
  282 + self.completionBlock = nil;
  283 + [self done];
  284 + }
  285 + else
277 { 286 {
278 - UIImage *image = [UIImage decodedImageWithImage:SDScaledImageForPath(self.request.URL.absoluteString, self.imageData)];  
279 - dispatch_async(dispatch_get_main_queue(), ^ 287 + dispatch_async(self.queue, ^
280 { 288 {
281 - if (CGSizeEqualToSize(image.size, CGSizeZero))  
282 - {  
283 - completionBlock(nil, nil, [NSError errorWithDomain:@"SDWebImageErrorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey: @"Downloaded image has 0 pixels"}], YES);  
284 - }  
285 - else 289 + UIImage *image = [UIImage decodedImageWithImage:SDScaledImageForPath(self.request.URL.absoluteString, self.imageData)];
  290 + dispatch_async(dispatch_get_main_queue(), ^
286 { 291 {
287 - completionBlock(image, self.imageData, nil, YES);  
288 - }  
289 - self.completionBlock = nil;  
290 - [self done]; 292 + if (CGSizeEqualToSize(image.size, CGSizeZero))
  293 + {
  294 + completionBlock(nil, nil, [NSError errorWithDomain:@"SDWebImageErrorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey: @"Downloaded image has 0 pixels"}], YES);
  295 + }
  296 + else
  297 + {
  298 + completionBlock(image, self.imageData, nil, YES);
  299 + }
  300 + self.completionBlock = nil;
  301 + [self done];
  302 + });
291 }); 303 });
292 - }); 304 + }
293 } 305 }
294 else 306 else
295 { 307 {
@@ -311,6 +323,7 @@ @@ -311,6 +323,7 @@
311 323
312 - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse 324 - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
313 { 325 {
  326 + responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
314 if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) 327 if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData)
315 { 328 {
316 // Prevents caching of responses 329 // Prevents caching of responses
@@ -33,10 +33,12 @@ typedef enum @@ -33,10 +33,12 @@ typedef enum
33 */ 33 */
34 SDWebImageProgressiveDownload = 1 << 3, 34 SDWebImageProgressiveDownload = 1 << 3,
35 /** 35 /**
36 - * Even if the image is cached, fetch the URL again anyway. When set, NSURLCache is enabled in the downloader.  
37 - * NSURLCache will handle the protocol caching logic while SDWebImage remains useful for offline images. 36 + * Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed.
  37 + * The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation.
38 * This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics. 38 * This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics.
39 - * If a cached image exists, the completion block is called once with the cached image and again with the final image. 39 + * If a cached image is refreshed, the completion block is called once with the cached image and again with the final image.
  40 + *
  41 + * Use this flag only if you can't make your URLs static with embeded cache busting parameter.
40 */ 42 */
41 SDWebImageRefreshCached = 1 << 4 43 SDWebImageRefreshCached = 1 << 4
42 } SDWebImageOptions; 44 } SDWebImageOptions;
@@ -107,8 +107,14 @@ @@ -107,8 +107,14 @@
107 SDWebImageDownloaderOptions downloaderOptions = 0; 107 SDWebImageDownloaderOptions downloaderOptions = 0;
108 if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority; 108 if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
109 if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload; 109 if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
110 - if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderEnableNSURLCache;  
111 - if (image && options & SDWebImageRefreshCached) downloaderOptions ^= SDWebImageDownloaderProgressiveDownload; // force progressive off if image is refreshing 110 + if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
  111 + if (image && options & SDWebImageRefreshCached)
  112 + {
  113 + // force progressive off if image already cached but forced refreshing
  114 + downloaderOptions ^= SDWebImageDownloaderProgressiveDownload;
  115 + // ignore image read from NSURLCache if image if cached but force refreshing
  116 + downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
  117 + }
112 __block id<SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) 118 __block id<SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished)
113 { 119 {
114 if (weakOperation.cancelled) 120 if (weakOperation.cancelled)
@@ -138,7 +144,11 @@ @@ -138,7 +144,11 @@
138 cacheOnDisk = NO; 144 cacheOnDisk = NO;
139 } 145 }
140 146
141 - if (downloadedImage && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) 147 + if (options & SDWebImageRefreshCached && image && !downloadedImage)
  148 + {
  149 + // Image refresh hit the NSURLCache cache, do not call the completion block
  150 + }
  151 + else if (downloadedImage && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)])
142 { 152 {
143 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ 153 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
144 { 154 {