From 898c96343cd9bdf9a541d6417e0984dfbc497383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Fournie=CC=81?= Date: Fri, 9 Dec 2011 17:56:13 +0100 Subject: [PATCH 1/9] Added 'httpHeaders' to ImageDownloader (to be able to set the 'Authorization' head for instance if needed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vincent Fournié --- SDWebImageDownloader.h | 5 ++++- SDWebImageDownloader.m | 24 ++++++++++++++++++++---- SDWebImageManager.h | 1 + SDWebImageManager.m | 21 ++++++++++++++++----- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/SDWebImageDownloader.h b/SDWebImageDownloader.h index 4b4183c60..76190b14c 100644 --- a/SDWebImageDownloader.h +++ b/SDWebImageDownloader.h @@ -22,6 +22,7 @@ extern NSString *const SDWebImageDownloadStopNotification; NSMutableData *imageData; id userInfo; BOOL lowPriority; + NSDictionary *httpHeaders; } @property (nonatomic, retain) NSURL *url; @@ -29,9 +30,11 @@ extern NSString *const SDWebImageDownloadStopNotification; @property (nonatomic, retain) NSMutableData *imageData; @property (nonatomic, retain) id userInfo; @property (nonatomic, readwrite) BOOL lowPriority; +@property (nonatomic, retain) NSDictionary *httpHeaders; -+ (id)downloaderWithURL:(NSURL *)url delegate:(id)delegate userInfo:(id)userInfo lowPriority:(BOOL)lowPriority; ++ (id)downloaderWithURL:(NSURL *)url delegate:(id)delegate userInfo:(id)userInfo lowPriority:(BOOL)lowPriority httpHeaders:(NSDictionary *)httpHeaders; + (id)downloaderWithURL:(NSURL *)url delegate:(id)delegate userInfo:(id)userInfo; ++ (id)downloaderWithURL:(NSURL *)url delegate:(id)delegate httpHeaders:(NSDictionary *)httpHeaders; + (id)downloaderWithURL:(NSURL *)url delegate:(id)delegate; - (void)start; - (void)cancel; diff --git a/SDWebImageDownloader.m b/SDWebImageDownloader.m index 52d599c92..9fb714d8c 100644 --- a/SDWebImageDownloader.m +++ b/SDWebImageDownloader.m @@ -22,7 +22,7 @@ @interface SDWebImageDownloader () @end @implementation SDWebImageDownloader -@synthesize url, delegate, connection, imageData, userInfo, lowPriority; +@synthesize url, delegate, connection, imageData, userInfo, lowPriority, httpHeaders; #pragma mark Public Methods @@ -31,13 +31,18 @@ + (id)downloaderWithURL:(NSURL *)url delegate:(id) return [self downloaderWithURL:url delegate:delegate userInfo:nil]; } ++ (id)downloaderWithURL:(NSURL *)url delegate:(id)delegate httpHeaders:(NSDictionary *)httpHeaders +{ + return [self downloaderWithURL:url delegate:delegate userInfo:nil lowPriority:NO httpHeaders:httpHeaders]; +} + + (id)downloaderWithURL:(NSURL *)url delegate:(id)delegate userInfo:(id)userInfo { - return [self downloaderWithURL:url delegate:delegate userInfo:userInfo lowPriority:NO]; + return [self downloaderWithURL:url delegate:delegate userInfo:userInfo lowPriority:NO httpHeaders:nil]; } -+ (id)downloaderWithURL:(NSURL *)url delegate:(id)delegate userInfo:(id)userInfo lowPriority:(BOOL)lowPriority ++ (id)downloaderWithURL:(NSURL *)url delegate:(id)delegate userInfo:(id)userInfo lowPriority:(BOOL)lowPriority httpHeaders:(NSDictionary *)httpHeaders { // Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator ) // To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import @@ -57,6 +62,7 @@ + (id)downloaderWithURL:(NSURL *)url delegate:(id) downloader.delegate = delegate; downloader.userInfo = userInfo; downloader.lowPriority = lowPriority; + downloader.httpHeaders = httpHeaders; [downloader performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:YES]; return downloader; } @@ -69,7 +75,12 @@ + (void)setMaxConcurrentDownloads:(NSUInteger)max - (void)start { // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests - NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15]; + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15]; + if ( self.httpHeaders ) { + for (NSString *key in [self.httpHeaders allKeys]) { + [request setValue:[self.httpHeaders objectForKey:key] forHTTPHeaderField:key]; + } + } self.connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO] autorelease]; // If not in low priority mode, ensure we aren't blocked by UI manipulations (default runloop mode for NSURLConnection is NSEventTrackingRunLoopMode) @@ -167,8 +178,13 @@ - (void)dealloc [connection release], connection = nil; [imageData release], imageData = nil; [userInfo release], userInfo = nil; + [httpHeaders release], httpHeaders = nil; [super dealloc]; } +//-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge +//{ +// NSLog( @"didReceiveAuthenticationChallenge" ); +//} @end diff --git a/SDWebImageManager.h b/SDWebImageManager.h index aef63ca24..5e720facb 100644 --- a/SDWebImageManager.h +++ b/SDWebImageManager.h @@ -32,6 +32,7 @@ typedef enum - (UIImage *)imageWithURL:(NSURL *)url; - (void)downloadWithURL:(NSURL *)url delegate:(id)delegate; - (void)downloadWithURL:(NSURL *)url delegate:(id)delegate options:(SDWebImageOptions)options; +- (void)downloadWithURL:(NSURL *)url delegate:(id)delegate options:(SDWebImageOptions)options httpHeaders:(NSDictionary *)httpHeaders; - (void)downloadWithURL:(NSURL *)url delegate:(id)delegate retryFailed:(BOOL)retryFailed __attribute__ ((deprecated)); // use options:SDWebImageRetryFailed instead - (void)downloadWithURL:(NSURL *)url delegate:(id)delegate retryFailed:(BOOL)retryFailed lowPriority:(BOOL)lowPriority __attribute__ ((deprecated)); // use options:SDWebImageRetryFailed|SDWebImageLowPriority instead - (void)cancelForDelegate:(id)delegate; diff --git a/SDWebImageManager.m b/SDWebImageManager.m index fabf0f0ed..5095a8989 100644 --- a/SDWebImageManager.m +++ b/SDWebImageManager.m @@ -63,7 +63,7 @@ - (UIImage *)imageWithURL:(NSURL *)url */ - (void)downloadWithURL:(NSURL *)url delegate:(id)delegate retryFailed:(BOOL)retryFailed { - [self downloadWithURL:url delegate:delegate options:(retryFailed ? SDWebImageRetryFailed : 0)]; + [self downloadWithURL:url delegate:delegate options:(retryFailed ? SDWebImageRetryFailed : 0) httpHeaders:nil]; } /** @@ -74,15 +74,20 @@ - (void)downloadWithURL:(NSURL *)url delegate:(id)del SDWebImageOptions options = 0; if (retryFailed) options |= SDWebImageRetryFailed; if (lowPriority) options |= SDWebImageLowPriority; - [self downloadWithURL:url delegate:delegate options:options]; + [self downloadWithURL:url delegate:delegate options:options httpHeaders:nil]; } - (void)downloadWithURL:(NSURL *)url delegate:(id)delegate { - [self downloadWithURL:url delegate:delegate options:0]; + [self downloadWithURL:url delegate:delegate options:0 httpHeaders:nil]; } - (void)downloadWithURL:(NSURL *)url delegate:(id)delegate options:(SDWebImageOptions)options +{ + [self downloadWithURL:url delegate:delegate options:options httpHeaders:nil]; +} + +- (void)downloadWithURL:(NSURL *)url delegate:(id)delegate options:(SDWebImageOptions)options httpHeaders:(NSDictionary *)httpHeaders { // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString. @@ -99,7 +104,12 @@ - (void)downloadWithURL:(NSURL *)url delegate:(id)del // Check the on-disk cache async so we don't block the main thread [cacheDelegates addObject:delegate]; [cacheURLs addObject:url]; - NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:delegate, @"delegate", url, @"url", [NSNumber numberWithInt:options], @"options", nil]; + NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys: + delegate, @"delegate", + url, @"url", + [NSNumber numberWithInt:options], @"options", + (httpHeaders ? httpHeaders : [NSDictionary dictionary]), @"httpHeaders", + nil]; [[SDImageCache sharedImageCache] queryDiskCacheForKey:[url absoluteString] delegate:self userInfo:info]; } @@ -172,6 +182,7 @@ - (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *) NSURL *url = [info objectForKey:@"url"]; id delegate = [info objectForKey:@"delegate"]; SDWebImageOptions options = [[info objectForKey:@"options"] intValue]; + NSDictionary *httpHeaders = [info objectForKey:@"httpHeaders"]; NSUInteger idx = [self indexOfDelegate:delegate waitingForURL:url]; if (idx == NSNotFound) @@ -188,7 +199,7 @@ - (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *) if (!downloader) { - downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self userInfo:info lowPriority:(options & SDWebImageLowPriority)]; + downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self userInfo:info lowPriority:(options & SDWebImageLowPriority) httpHeaders:httpHeaders]; [downloaderForURL setObject:downloader forKey:url]; } else From 10d0ccaf910baac326414d32ce7b01b665b96f4f Mon Sep 17 00:00:00 2001 From: Claire Reynaud Date: Tue, 3 Jan 2012 17:26:41 +0100 Subject: [PATCH 2/9] Add etag support in SDImageCache This is a 1st implementation. There is no way to specify whether the server should be requested again after a given amount of time only. When the SDWebImageCacheUseETags option is used, the server is requested every time an image is set via the setImageWithURL call. --- SDImageCache.h | 2 +- SDImageCache.m | 62 +++++++++++++++++++++++++++------- SDImageCacheDelegate.h | 2 +- SDWebImageDownloader.h | 4 +++ SDWebImageDownloader.m | 37 ++++++++++++++++---- SDWebImageDownloaderDelegate.h | 4 +-- SDWebImageManager.h | 3 +- SDWebImageManager.m | 37 ++++++++++++++++---- 8 files changed, 121 insertions(+), 30 deletions(-) diff --git a/SDImageCache.h b/SDImageCache.h index f2abd1205..e7d1142f0 100644 --- a/SDImageCache.h +++ b/SDImageCache.h @@ -19,7 +19,7 @@ + (SDImageCache *)sharedImageCache; - (void)storeImage:(UIImage *)image forKey:(NSString *)key; - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk; -- (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk; +- (void)storeImage:(UIImage *)image imageData:(NSData *)data withEtag:(NSString *)etag forKey:(NSString *)key toDisk:(BOOL)toDisk; - (UIImage *)imageFromKey:(NSString *)key; - (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk; - (void)queryDiskCacheForKey:(NSString *)key delegate:(id )delegate userInfo:(NSDictionary *)info; diff --git a/SDImageCache.m b/SDImageCache.m index 44671b880..5f7a70e67 100644 --- a/SDImageCache.m +++ b/SDImageCache.m @@ -8,6 +8,7 @@ #import "SDImageCache.h" #import "SDWebImageDecoder.h" +#import "SDWebImageManager.h" #import #ifdef ENABLE_SDWEBIMAGE_DECODER @@ -101,17 +102,29 @@ + (SDImageCache *)sharedImageCache #pragma mark SDImageCache (private) -- (NSString *)cachePathForKey:(NSString *)key +- (NSString *)filnameForKey:(NSString *)key { const char *str = [key UTF8String]; 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", r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15]]; + return filename; +} +- (NSString *)cachePathForKey:(NSString *)key +{ + NSString *filename = [self filnameForKey:key]; return [diskCachePath stringByAppendingPathComponent:filename]; } +- (NSString *)cachePathForKeyEtag:(NSString *)key +{ + NSString *filename = [self filnameForKey:key]; + NSString *filenameForEtag = [NSString stringWithFormat:@"%@-etag", filename]; + return [diskCachePath stringByAppendingPathComponent:filenameForEtag]; +} + - (void)storeKeyWithDataToDisk:(NSArray *)keyAndData { // Can't use defaultManager another thread @@ -119,10 +132,15 @@ - (void)storeKeyWithDataToDisk:(NSArray *)keyAndData NSString *key = [keyAndData objectAtIndex:0]; NSData *data = [keyAndData count] > 1 ? [keyAndData objectAtIndex:1] : nil; + NSString *etag = [keyAndData count] > 2 ? [keyAndData objectAtIndex:2] : nil; if (data) { [fileManager createFileAtPath:[self cachePathForKey:key] contents:data attributes:nil]; + if (etag) { + const char *utfEtag = [etag UTF8String]; + [fileManager createFileAtPath:[self cachePathForKeyEtag:key] contents:[NSData dataWithBytes:utfEtag length:strlen(utfEtag)] attributes:nil]; + } } else { @@ -149,19 +167,28 @@ - (void)notifyDelegate:(NSDictionary *)arguments { NSString *key = [arguments objectForKey:@"key"]; id delegate = [arguments objectForKey:@"delegate"]; - NSDictionary *info = [arguments objectForKey:@"userInfo"]; + NSMutableDictionary *info = [[[NSMutableDictionary alloc] initWithDictionary:[arguments objectForKey:@"userInfo"]] autorelease]; UIImage *image = [arguments objectForKey:@"image"]; + NSString *etag = [arguments objectForKey:@"etag"]; + if (etag) { + [info setObject:etag forKey:@"etag"]; + } + BOOL doRequest = YES; if (image) { [memCache setObject:image forKey:key]; - if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:)]) + if (![[info objectForKey:@"options"] intValue] & SDWebImageCacheUseETags) + { + doRequest = NO; + } + if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:removeDelegate:)]) { - [delegate imageCache:self didFindImage:image forKey:key userInfo:info]; + [delegate imageCache:self didFindImage:image forKey:key userInfo:info removeDelegate:!doRequest]; } } - else + if (doRequest) { if ([delegate respondsToSelector:@selector(imageCache:didNotFindImageForKey:userInfo:)]) { @@ -186,6 +213,9 @@ - (void)queryDiskCacheOperation:(NSDictionary *)arguments } #endif [mutableArguments setObject:image forKey:@"image"]; + + NSString *etag = [[[NSString alloc] initWithContentsOfFile:[self cachePathForKeyEtag:key] encoding:NSUTF8StringEncoding error:nil] autorelease]; + [mutableArguments setObject:etag forKey:@"etag"]; } [self performSelectorOnMainThread:@selector(notifyDelegate:) withObject:mutableArguments waitUntilDone:NO]; @@ -193,7 +223,7 @@ - (void)queryDiskCacheOperation:(NSDictionary *)arguments #pragma mark ImageCache -- (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk +- (void)storeImage:(UIImage *)image imageData:(NSData *)data withEtag:(NSString *)etag forKey:(NSString *)key toDisk:(BOOL)toDisk { if (!image || !key) { @@ -208,7 +238,11 @@ - (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *) NSArray *keyWithData; if (data) { - keyWithData = [NSArray arrayWithObjects:key, data, nil]; + if (etag) { + keyWithData = [NSArray arrayWithObjects:key, data, etag, nil]; + } else { + keyWithData = [NSArray arrayWithObjects:key, data, nil]; + } } else { @@ -222,12 +256,12 @@ - (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *) - (void)storeImage:(UIImage *)image forKey:(NSString *)key { - [self storeImage:image imageData:nil forKey:key toDisk:YES]; + [self storeImage:image imageData:nil withEtag:nil forKey:key toDisk:YES]; } - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk { - [self storeImage:image imageData:nil forKey:key toDisk:toDisk]; + [self storeImage:image imageData:nil withEtag:nil forKey:key toDisk:toDisk]; } @@ -277,12 +311,16 @@ - (void)queryDiskCacheForKey:(NSString *)key delegate:(id UIImage *image = [memCache objectForKey:key]; if (image) { + BOOL useEtags = [[info objectForKey:@"options"] intValue] & SDWebImageCacheUseETags; // ...notify delegate immediately, no need to go async - if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:)]) + if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:removeDelegate:)]) { - [delegate imageCache:self didFindImage:image forKey:key userInfo:info]; + [delegate imageCache:self didFindImage:image forKey:key userInfo:info removeDelegate:!useEtags]; + } + if (!useEtags) + { + return; } - return; } NSMutableDictionary *arguments = [NSMutableDictionary dictionaryWithCapacity:3]; diff --git a/SDImageCacheDelegate.h b/SDImageCacheDelegate.h index 85d1d16bc..bc9ffa8ad 100644 --- a/SDImageCacheDelegate.h +++ b/SDImageCacheDelegate.h @@ -13,7 +13,7 @@ @protocol SDImageCacheDelegate @optional -- (void)imageCache:(SDImageCache *)imageCache didFindImage:(UIImage *)image forKey:(NSString *)key userInfo:(NSDictionary *)info; +- (void)imageCache:(SDImageCache *)imageCache didFindImage:(UIImage *)image forKey:(NSString *)key userInfo:(NSDictionary *)info removeDelegate:(BOOL)doRemoveDelegate; - (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *)key userInfo:(NSDictionary *)info; @end diff --git a/SDWebImageDownloader.h b/SDWebImageDownloader.h index 76190b14c..f29763e34 100644 --- a/SDWebImageDownloader.h +++ b/SDWebImageDownloader.h @@ -20,6 +20,8 @@ extern NSString *const SDWebImageDownloadStopNotification; id delegate; NSURLConnection *connection; NSMutableData *imageData; + NSMutableString *etag; + NSInteger statusCode; id userInfo; BOOL lowPriority; NSDictionary *httpHeaders; @@ -28,8 +30,10 @@ extern NSString *const SDWebImageDownloadStopNotification; @property (nonatomic, retain) NSURL *url; @property (nonatomic, assign) id delegate; @property (nonatomic, retain) NSMutableData *imageData; +@property (nonatomic, retain) NSMutableString *etag; @property (nonatomic, retain) id userInfo; @property (nonatomic, readwrite) BOOL lowPriority; +@property (nonatomic, readwrite) NSInteger statusCode; @property (nonatomic, retain) NSDictionary *httpHeaders; + (id)downloaderWithURL:(NSURL *)url delegate:(id)delegate userInfo:(id)userInfo lowPriority:(BOOL)lowPriority httpHeaders:(NSDictionary *)httpHeaders; diff --git a/SDWebImageDownloader.m b/SDWebImageDownloader.m index 9fb714d8c..83d2f09a1 100644 --- a/SDWebImageDownloader.m +++ b/SDWebImageDownloader.m @@ -6,6 +6,7 @@ * file that was distributed with this source code. */ +#import #import "SDWebImageDownloader.h" #ifdef ENABLE_SDWEBIMAGE_DECODER @@ -22,7 +23,7 @@ @interface SDWebImageDownloader () @end @implementation SDWebImageDownloader -@synthesize url, delegate, connection, imageData, userInfo, lowPriority, httpHeaders; +@synthesize url, delegate, connection, imageData, etag, userInfo, lowPriority, httpHeaders, statusCode; #pragma mark Public Methods @@ -117,6 +118,16 @@ - (void)cancel #pragma mark NSURLConnection (delegate) +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSHTTPURLResponse *httpurlResponse = (NSHTTPURLResponse *)response; + NSDictionary *headers = [httpurlResponse allHeaderFields]; + self.etag = [headers objectForKey:@"Etag"]; + self.statusCode = [httpurlResponse statusCode]; + } +} + - (void)connection:(NSURLConnection *)aConnection didReceiveData:(NSData *)data { [imageData appendData:data]; @@ -134,16 +145,26 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)aConnection [delegate performSelector:@selector(imageDownloaderDidFinish:) withObject:self]; } - if ([delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:)]) - { - UIImage *image = [[UIImage alloc] initWithData:imageData]; + if (self.statusCode == 304) { + if ([delegate respondsToSelector:@selector(imageDownloaderDidFinishWithSameImage:)]) + { + objc_msgSend(delegate, @selector(imageDownloaderDidFinishWithSameImage:), self); + } + } else { + if ([delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:andEtag:)]) { + UIImage *image = [[UIImage alloc] initWithData:imageData]; + NSString *etagCopy; + if (self.etag) { + etagCopy = [[NSString alloc] initWithString:self.etag]; + } #ifdef ENABLE_SDWEBIMAGE_DECODER - [[SDWebImageDecoder sharedImageDecoder] decodeImage:image withDelegate:self userInfo:nil]; + [[SDWebImageDecoder sharedImageDecoder] decodeImage:image withDelegate:self userInfo:nil]; #else - [delegate performSelector:@selector(imageDownloader:didFinishWithImage:) withObject:self withObject:image]; + objc_msgSend(delegate, @selector(imageDownloader:didFinishWithImage:andEtag:), self, image, etagCopy); #endif - [image release]; + [image release]; + } } } @@ -158,6 +179,7 @@ - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)err self.connection = nil; self.imageData = nil; + self.etag = nil; } #pragma mark SDWebImageDecoderDelegate @@ -177,6 +199,7 @@ - (void)dealloc [url release], url = nil; [connection release], connection = nil; [imageData release], imageData = nil; + [etag release], etag = nil; [userInfo release], userInfo = nil; [httpHeaders release], httpHeaders = nil; [super dealloc]; diff --git a/SDWebImageDownloaderDelegate.h b/SDWebImageDownloaderDelegate.h index c33f87b0a..28d9bbacf 100644 --- a/SDWebImageDownloaderDelegate.h +++ b/SDWebImageDownloaderDelegate.h @@ -14,8 +14,8 @@ @optional -- (void)imageDownloaderDidFinish:(SDWebImageDownloader *)downloader; -- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image; +- (void)imageDownloaderDidFinishWithSameImage:(SDWebImageDownloader *)downloader; +- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image andEtag:(NSString *)etag; - (void)imageDownloader:(SDWebImageDownloader *)downloader didFailWithError:(NSError *)error; @end diff --git a/SDWebImageManager.h b/SDWebImageManager.h index 5e720facb..c1f7ce48b 100644 --- a/SDWebImageManager.h +++ b/SDWebImageManager.h @@ -15,7 +15,8 @@ typedef enum { SDWebImageRetryFailed = 1 << 0, SDWebImageLowPriority = 1 << 1, - SDWebImageCacheMemoryOnly = 1 << 2 + SDWebImageCacheMemoryOnly = 1 << 2, + SDWebImageCacheUseETags = 1 << 3 } SDWebImageOptions; @interface SDWebImageManager : NSObject diff --git a/SDWebImageManager.m b/SDWebImageManager.m index 5095a8989..4572566bd 100644 --- a/SDWebImageManager.m +++ b/SDWebImageManager.m @@ -156,7 +156,7 @@ - (NSUInteger)indexOfDelegate:(id)delegate waitingFor return NSNotFound; } -- (void)imageCache:(SDImageCache *)imageCache didFindImage:(UIImage *)image forKey:(NSString *)key userInfo:(NSDictionary *)info +- (void)imageCache:(SDImageCache *)imageCache didFindImage:(UIImage *)image forKey:(NSString *)key userInfo:(NSDictionary *)info removeDelegate:(BOOL)doRemoveDelegate { NSURL *url = [info objectForKey:@"url"]; id delegate = [info objectForKey:@"delegate"]; @@ -173,8 +173,11 @@ - (void)imageCache:(SDImageCache *)imageCache didFindImage:(UIImage *)image forK [delegate performSelector:@selector(webImageManager:didFinishWithImage:) withObject:self withObject:image]; } - [cacheDelegates removeObjectAtIndex:idx]; - [cacheURLs removeObjectAtIndex:idx]; + if (doRemoveDelegate) + { + [cacheDelegates removeObjectAtIndex:idx]; + [cacheURLs removeObjectAtIndex:idx]; + } } - (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *)key userInfo:(NSDictionary *)info @@ -182,7 +185,11 @@ - (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *) NSURL *url = [info objectForKey:@"url"]; id delegate = [info objectForKey:@"delegate"]; SDWebImageOptions options = [[info objectForKey:@"options"] intValue]; - NSDictionary *httpHeaders = [info objectForKey:@"httpHeaders"]; + NSString *etag = [info objectForKey:@"etag"]; + NSMutableDictionary *httpHeaders = [[[NSMutableDictionary alloc] initWithDictionary:[info objectForKey:@"httpHeaders"]] autorelease]; + if (etag) { + [httpHeaders setObject:etag forKey:@"If-None-Match"]; + } NSUInteger idx = [self indexOfDelegate:delegate waitingForURL:url]; if (idx == NSNotFound) @@ -215,7 +222,25 @@ - (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *) #pragma mark SDWebImageDownloaderDelegate -- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image +- (void)imageDownloaderDidFinishWithSameImage:(SDWebImageDownloader *)downloader { + [downloader retain]; + + // Notify all the downloadDelegates with this downloader + for (NSInteger idx = (NSInteger) [downloaders count] - 1; idx >= 0; idx--) { + NSUInteger uidx = (NSUInteger) idx; + SDWebImageDownloader *aDownloader = [downloaders objectAtIndex:uidx]; + if (aDownloader == downloader) { + [downloaders removeObjectAtIndex:uidx]; + [downloadDelegates removeObjectAtIndex:uidx]; + } + } + + // Release the downloader + [downloaderForURL removeObjectForKey:downloader.url]; + [downloader release]; +} + +- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image andEtag:(NSString *)etag { [downloader retain]; SDWebImageOptions options = [[downloader.userInfo objectForKey:@"options"] intValue]; @@ -253,7 +278,7 @@ - (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(U { // Store the image in the cache [[SDImageCache sharedImageCache] storeImage:image - imageData:downloader.imageData + imageData:downloader.imageData withEtag:etag forKey:[downloader.url absoluteString] toDisk:!(options & SDWebImageCacheMemoryOnly)]; } From 2e76a1365d8dbc55ea4db92d87b35064cc093327 Mon Sep 17 00:00:00 2001 From: Claire Reynaud Date: Wed, 4 Jan 2012 11:33:10 +0100 Subject: [PATCH 3/9] Request server with etag only after a given time interval. Default is 15 minutes. --- SDImageCache.h | 3 +++ SDImageCache.m | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/SDImageCache.h b/SDImageCache.h index e7d1142f0..0079983cd 100644 --- a/SDImageCache.h +++ b/SDImageCache.h @@ -12,11 +12,14 @@ @interface SDImageCache : NSObject { NSMutableDictionary *memCache; + NSMutableDictionary *memCacheLastUpdateTime; NSString *diskCachePath; NSOperationQueue *cacheInQueue, *cacheOutQueue; + NSTimeInterval etagsQuerytimeInterval; } + (SDImageCache *)sharedImageCache; +- (void)setEtagsQueryTimeInterval:(NSTimeInterval)timeInterval; - (void)storeImage:(UIImage *)image forKey:(NSString *)key; - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk; - (void)storeImage:(UIImage *)image imageData:(NSData *)data withEtag:(NSString *)etag forKey:(NSString *)key toDisk:(BOOL)toDisk; diff --git a/SDImageCache.m b/SDImageCache.m index 5f7a70e67..1a1dc15a1 100644 --- a/SDImageCache.m +++ b/SDImageCache.m @@ -29,6 +29,10 @@ - (id)init { // Init the memory cache memCache = [[NSMutableDictionary alloc] init]; + memCacheLastUpdateTime = [[NSMutableDictionary alloc] init]; + + // Default etags query time interval is 15 minutes + etagsQuerytimeInterval = 15*60; // Init the disk cache NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); @@ -80,6 +84,7 @@ - (id)init - (void)dealloc { [memCache release], memCache = nil; + [memCacheLastUpdateTime release], memCacheLastUpdateTime = nil; [diskCachePath release], diskCachePath = nil; [cacheInQueue release], cacheInQueue = nil; @@ -102,6 +107,13 @@ + (SDImageCache *)sharedImageCache #pragma mark SDImageCache (private) +- (void)setImageInMemCache:(UIImage *)image forKey:(NSString *)key +{ + [memCache setObject:image forKey:key]; + NSDate *date = [NSDate date]; + [memCacheLastUpdateTime setObject:date forKey:key]; +} + - (NSString *)filnameForKey:(NSString *)key { const char *str = [key UTF8String]; @@ -177,7 +189,7 @@ - (void)notifyDelegate:(NSDictionary *)arguments BOOL doRequest = YES; if (image) { - [memCache setObject:image forKey:key]; + [self setImageInMemCache:image forKey:key]; if (![[info objectForKey:@"options"] intValue] & SDWebImageCacheUseETags) { @@ -223,6 +235,11 @@ - (void)queryDiskCacheOperation:(NSDictionary *)arguments #pragma mark ImageCache +- (void)setEtagsQueryTimeInterval:(NSTimeInterval)timeInterval +{ + etagsQuerytimeInterval = timeInterval; +} + - (void)storeImage:(UIImage *)image imageData:(NSData *)data withEtag:(NSString *)etag forKey:(NSString *)key toDisk:(BOOL)toDisk { if (!image || !key) @@ -230,7 +247,7 @@ - (void)storeImage:(UIImage *)image imageData:(NSData *)data withEtag:(NSString return; } - [memCache setObject:image forKey:key]; + [self setImageInMemCache:image forKey:key]; if (toDisk) { @@ -284,7 +301,7 @@ - (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk image = [[[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]] autorelease]; if (image) { - [memCache setObject:image forKey:key]; + [self setImageInMemCache:image forKey:key]; } } @@ -309,9 +326,18 @@ - (void)queryDiskCacheForKey:(NSString *)key delegate:(id // First check the in-memory cache... UIImage *image = [memCache objectForKey:key]; + BOOL useEtags = [[info objectForKey:@"options"] intValue] & SDWebImageCacheUseETags; + + NSDate *lastUpdateTime = [memCacheLastUpdateTime objectForKey:key]; + NSDate *currentTime = [NSDate date]; + NSDate *lastUpdateTimePlusDelay = [NSDate dateWithTimeInterval:etagsQuerytimeInterval sinceDate:lastUpdateTime]; + if ([lastUpdateTimePlusDelay compare:currentTime] == NSOrderedDescending) + { + useEtags = NO; + } + if (image) { - BOOL useEtags = [[info objectForKey:@"options"] intValue] & SDWebImageCacheUseETags; // ...notify delegate immediately, no need to go async if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:removeDelegate:)]) { From 64064ed021eff7aeb793932e1bfcc82d36d77f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Fournie=CC=81?= Date: Wed, 11 Jan 2012 17:42:44 +0100 Subject: [PATCH 4/9] Fixed potential memory leak detected by clang static analyser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vincent Fournié --- SDWebImageDownloader.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SDWebImageDownloader.m b/SDWebImageDownloader.m index 83d2f09a1..e724e3fdd 100644 --- a/SDWebImageDownloader.m +++ b/SDWebImageDownloader.m @@ -153,9 +153,9 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)aConnection } else { if ([delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:andEtag:)]) { UIImage *image = [[UIImage alloc] initWithData:imageData]; - NSString *etagCopy; + NSString *etagCopy = nil; if (self.etag) { - etagCopy = [[NSString alloc] initWithString:self.etag]; + etagCopy = [NSString stringWithString:self.etag]; } #ifdef ENABLE_SDWEBIMAGE_DECODER From bf6ad06bbb36cf771fbf96e3b78a0dee3800ac60 Mon Sep 17 00:00:00 2001 From: Claire Reynaud Date: Fri, 17 Feb 2012 16:45:06 +0100 Subject: [PATCH 5/9] Fix image not being downloaded to cache if app does not run 15 minutes When an image is 1st added in the memory cache, set its timestamp to 1970 to make sure a request with etag will be made to the server. Before this fix an image update would never be downloaded if the calling app was running less than 15 minutes. --- SDImageCache.m | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/SDImageCache.m b/SDImageCache.m index 1a1dc15a1..e2be74d54 100644 --- a/SDImageCache.m +++ b/SDImageCache.m @@ -110,8 +110,13 @@ + (SDImageCache *)sharedImageCache - (void)setImageInMemCache:(UIImage *)image forKey:(NSString *)key { [memCache setObject:image forKey:key]; - NSDate *date = [NSDate date]; - [memCacheLastUpdateTime setObject:date forKey:key]; + if ([memCacheLastUpdateTime objectForKey:key]) { + NSDate *date = [NSDate date]; + [memCacheLastUpdateTime setObject:date forKey:key]; + } else { + NSDate *oldDate = [NSDate dateWithTimeIntervalSince1970:0]; + [memCacheLastUpdateTime setObject:oldDate forKey:key]; + } } - (NSString *)filnameForKey:(NSString *)key From 753f7fbfeac31acfb7d9c55a61a60a6902c1f481 Mon Sep 17 00:00:00 2001 From: Claire Reynaud Date: Fri, 25 May 2012 14:13:27 +0200 Subject: [PATCH 6/9] Fix exception when image does not have an etag --- SDImageCache.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SDImageCache.m b/SDImageCache.m index e2be74d54..2b16ddb6b 100644 --- a/SDImageCache.m +++ b/SDImageCache.m @@ -232,7 +232,9 @@ - (void)queryDiskCacheOperation:(NSDictionary *)arguments [mutableArguments setObject:image forKey:@"image"]; NSString *etag = [[[NSString alloc] initWithContentsOfFile:[self cachePathForKeyEtag:key] encoding:NSUTF8StringEncoding error:nil] autorelease]; - [mutableArguments setObject:etag forKey:@"etag"]; + if (etag) { + [mutableArguments setObject:etag forKey:@"etag"]; + } } [self performSelectorOnMainThread:@selector(notifyDelegate:) withObject:mutableArguments waitUntilDone:NO]; From d878b211c277f3cdc42ad6b04c6ec15552d93e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Fournie=CC=81?= Date: Mon, 9 Sep 2013 14:08:08 +0200 Subject: [PATCH 7/9] Fixed potential set nil object to dictionary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vincent Fournié --- SDImageCache.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SDImageCache.m b/SDImageCache.m index e2be74d54..2b16ddb6b 100644 --- a/SDImageCache.m +++ b/SDImageCache.m @@ -232,7 +232,9 @@ - (void)queryDiskCacheOperation:(NSDictionary *)arguments [mutableArguments setObject:image forKey:@"image"]; NSString *etag = [[[NSString alloc] initWithContentsOfFile:[self cachePathForKeyEtag:key] encoding:NSUTF8StringEncoding error:nil] autorelease]; - [mutableArguments setObject:etag forKey:@"etag"]; + if (etag) { + [mutableArguments setObject:etag forKey:@"etag"]; + } } [self performSelectorOnMainThread:@selector(notifyDelegate:) withObject:mutableArguments waitUntilDone:NO]; From e6a02852c4bdbd64b9ca7bce4d611bd951aed8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Fournie=CC=81?= Date: Mon, 11 May 2015 13:48:13 +0200 Subject: [PATCH 8/9] Migration 64 bits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vincent Fournié --- SDWebImageDownloader.m | 4 ++-- SDWebImagePrefetcher.m | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SDWebImageDownloader.m b/SDWebImageDownloader.m index e724e3fdd..528138510 100644 --- a/SDWebImageDownloader.m +++ b/SDWebImageDownloader.m @@ -148,7 +148,7 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)aConnection if (self.statusCode == 304) { if ([delegate respondsToSelector:@selector(imageDownloaderDidFinishWithSameImage:)]) { - objc_msgSend(delegate, @selector(imageDownloaderDidFinishWithSameImage:), self); + ((void(*)(id, SEL, id))objc_msgSend)(delegate, @selector(imageDownloaderDidFinishWithSameImage:), self); } } else { if ([delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:andEtag:)]) { @@ -161,7 +161,7 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)aConnection #ifdef ENABLE_SDWEBIMAGE_DECODER [[SDWebImageDecoder sharedImageDecoder] decodeImage:image withDelegate:self userInfo:nil]; #else - objc_msgSend(delegate, @selector(imageDownloader:didFinishWithImage:andEtag:), self, image, etagCopy); + ((void(*)(id, SEL, id, UIImage *, NSString *))objc_msgSend)(delegate, @selector(imageDownloader:didFinishWithImage:andEtag:), self, image, etagCopy); #endif [image release]; } diff --git a/SDWebImagePrefetcher.m b/SDWebImagePrefetcher.m index 7dbf35c35..27396cfb4 100644 --- a/SDWebImagePrefetcher.m +++ b/SDWebImagePrefetcher.m @@ -41,7 +41,7 @@ - (void)startPrefetchingAtIndex:(NSUInteger)index withManager:(SDWebImageManager - (void)reportStatus { NSUInteger total = [self.prefetchURLs count]; - NSLog(@"Finished prefetching (%d successful, %d skipped, timeElasped %.2f)", total - _skippedCount, _skippedCount, CFAbsoluteTimeGetCurrent() - _startedTime); + NSLog(@"Finished prefetching (%lu successful, %lu skipped, timeElasped %.2f)", total - _skippedCount, (unsigned long)_skippedCount, CFAbsoluteTimeGetCurrent() - _startedTime); } - (void)prefetchURLs:(NSArray *)urls @@ -51,7 +51,7 @@ - (void)prefetchURLs:(NSArray *)urls self.prefetchURLs = urls; // Starts prefetching from the very first image on the list with the max allowed concurrency - int listCount = [self.prefetchURLs count]; + int listCount = (int)[self.prefetchURLs count]; SDWebImageManager *manager = [SDWebImageManager sharedManager]; for (int i = 0; i < self.maxConcurrentDownloads && _requestedCount < listCount; i++) { @@ -73,7 +73,7 @@ - (void)cancelPrefetching - (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image { _finishedCount++; - NSLog(@"Prefetched %d out of %d", _finishedCount, [self.prefetchURLs count]); + NSLog(@"Prefetched %lu out of %lu", (unsigned long)_finishedCount, (unsigned long)[self.prefetchURLs count]); if ([self.prefetchURLs count] > _requestedCount) { @@ -88,7 +88,7 @@ - (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UI - (void)webImageManager:(SDWebImageManager *)imageManager didFailWithError:(NSError *)error { _finishedCount++; - NSLog(@"Prefetched %d out of %d (Failed)", _finishedCount, [self.prefetchURLs count]); + NSLog(@"Prefetched %lu out of %lu (Failed)", (unsigned long)_finishedCount, (unsigned long)[self.prefetchURLs count]); // Add last failed _skippedCount++; From ec970ef3da765e8d5b9bea260bdabd1390c78d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Fournie=CC=81?= Date: Wed, 20 May 2015 11:49:09 +0200 Subject: [PATCH 9/9] Fix 64-bit migration warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vincent Fournié --- SDWebImagePrefetcher.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDWebImagePrefetcher.m b/SDWebImagePrefetcher.m index 27396cfb4..322c47d35 100644 --- a/SDWebImagePrefetcher.m +++ b/SDWebImagePrefetcher.m @@ -41,7 +41,7 @@ - (void)startPrefetchingAtIndex:(NSUInteger)index withManager:(SDWebImageManager - (void)reportStatus { NSUInteger total = [self.prefetchURLs count]; - NSLog(@"Finished prefetching (%lu successful, %lu skipped, timeElasped %.2f)", total - _skippedCount, (unsigned long)_skippedCount, CFAbsoluteTimeGetCurrent() - _startedTime); + NSLog(@"Finished prefetching (%lu successful, %lu skipped, timeElasped %.2f)", (unsigned long)(total - _skippedCount), (unsigned long)_skippedCount, CFAbsoluteTimeGetCurrent() - _startedTime); } - (void)prefetchURLs:(NSArray *)urls