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
{
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1,
SDWebImageDownloaderEnableNSURLCache = 1 << 2
/**
* By default, request prevent the of NSURLCache. With this flag, NSURLCache
* is used with default policies.
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2,
/**
* Call completion block with nil image/imageData if the image was read from NSURLCache
* (to be combined with `SDWebImageDownloaderUseNSURLCache`).
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3
} SDWebImageDownloaderOptions;
typedef enum
... ...
... ... @@ -120,7 +120,7 @@ static NSString *const kCompletedCallbackKey = @"completed";
[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 if told otherwise
NSMutableURLRequest *request = [NSMutableURLRequest.alloc initWithURL:url cachePolicy:(options & SDWebImageDownloaderEnableNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:15];
NSMutableURLRequest *request = [NSMutableURLRequest.alloc initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:15];
request.HTTPShouldHandleCookies = NO;
request.HTTPShouldUsePipelining = YES;
request.allHTTPHeaderFields = wself.HTTPHeaders;
... ...
... ... @@ -28,6 +28,7 @@
@implementation SDWebImageDownloaderOperation
{
size_t width, height;
BOOL responseFromCached;
}
- (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 @@
_executing = NO;
_finished = NO;
_expectedSize = 0;
responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called
}
return self;
}
... ... @@ -271,25 +273,35 @@
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:nil];
SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
if (completionBlock)
{
dispatch_async(self.queue, ^
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached)
{
completionBlock(nil, nil, nil, YES);
self.completionBlock = nil;
[self done];
}
else
{
UIImage *image = [UIImage decodedImageWithImage:SDScaledImageForPath(self.request.URL.absoluteString, self.imageData)];
dispatch_async(dispatch_get_main_queue(), ^
dispatch_async(self.queue, ^
{
if (CGSizeEqualToSize(image.size, CGSizeZero))
{
completionBlock(nil, nil, [NSError errorWithDomain:@"SDWebImageErrorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey: @"Downloaded image has 0 pixels"}], YES);
}
else
UIImage *image = [UIImage decodedImageWithImage:SDScaledImageForPath(self.request.URL.absoluteString, self.imageData)];
dispatch_async(dispatch_get_main_queue(), ^
{
completionBlock(image, self.imageData, nil, YES);
}
self.completionBlock = nil;
[self done];
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);
}
self.completionBlock = nil;
[self done];
});
});
});
}
}
else
{
... ... @@ -311,6 +323,7 @@
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData)
{
// Prevents caching of responses
... ...
... ... @@ -33,10 +33,12 @@ typedef enum
*/
SDWebImageProgressiveDownload = 1 << 3,
/**
* Even if the image is cached, fetch the URL again anyway. When set, NSURLCache is enabled in the downloader.
* NSURLCache will handle the protocol caching logic while SDWebImage remains useful for offline images.
* Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed.
* The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation.
* This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics.
* If a cached image exists, the completion block is called once with the cached image and again with the final image.
* If a cached image is refreshed, the completion block is called once with the cached image and again with the final image.
*
* Use this flag only if you can't make your URLs static with embeded cache busting parameter.
*/
SDWebImageRefreshCached = 1 << 4
} SDWebImageOptions;
... ...
... ... @@ -107,8 +107,14 @@
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderEnableNSURLCache;
if (image && options & SDWebImageRefreshCached) downloaderOptions ^= SDWebImageDownloaderProgressiveDownload; // force progressive off if image is refreshing
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (image && options & SDWebImageRefreshCached)
{
// force progressive off if image already cached but forced refreshing
downloaderOptions ^= SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
__block id<SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished)
{
if (weakOperation.cancelled)
... ... @@ -138,7 +144,11 @@
cacheOnDisk = NO;
}
if (downloadedImage && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)])
if (options & SDWebImageRefreshCached && image && !downloadedImage)
{
// Image refresh hit the NSURLCache cache, do not call the completion block
}
else if (downloadedImage && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)])
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
{
... ...