Authored by Olivier Poitrey

Perform disk cache out operations asynchronousely in order to prevent from block…

…ing the main runloop when a lot of cache queries are performed at the same time
@@ -7,12 +7,13 @@ @@ -7,12 +7,13 @@
7 */ 7 */
8 8
9 #import <Foundation/Foundation.h> 9 #import <Foundation/Foundation.h>
  10 +#import "SDImageCacheDelegate.h"
10 11
11 @interface SDImageCache : NSObject 12 @interface SDImageCache : NSObject
12 { 13 {
13 NSMutableDictionary *memCache, *storeDataQueue; 14 NSMutableDictionary *memCache, *storeDataQueue;
14 NSString *diskCachePath; 15 NSString *diskCachePath;
15 - NSOperationQueue *cacheInQueue; 16 + NSOperationQueue *cacheInQueue, *cacheOutQueue;
16 } 17 }
17 18
18 + (SDImageCache *)sharedImageCache; 19 + (SDImageCache *)sharedImageCache;
@@ -21,6 +22,8 @@ @@ -21,6 +22,8 @@
21 - (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk; 22 - (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk;
22 - (UIImage *)imageFromKey:(NSString *)key; 23 - (UIImage *)imageFromKey:(NSString *)key;
23 - (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk; 24 - (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk;
  25 +- (void)queryDiskCacheForKey:(NSString *)key delegate:(id <SDImageCacheDelegate>)delegate userInfo:(NSDictionary *)info;
  26 +
24 - (void)removeImageForKey:(NSString *)key; 27 - (void)removeImageForKey:(NSString *)key;
25 - (void)clearMemory; 28 - (void)clearMemory;
26 - (void)clearDisk; 29 - (void)clearDisk;
@@ -39,7 +39,9 @@ static SDImageCache *instance; @@ -39,7 +39,9 @@ static SDImageCache *instance;
39 39
40 // Init the operation queue 40 // Init the operation queue
41 cacheInQueue = [[NSOperationQueue alloc] init]; 41 cacheInQueue = [[NSOperationQueue alloc] init];
42 - cacheInQueue.maxConcurrentOperationCount = 2; 42 + cacheInQueue.maxConcurrentOperationCount = 1;
  43 + cacheOutQueue = [[NSOperationQueue alloc] init];
  44 + cacheOutQueue.maxConcurrentOperationCount = 1;
43 45
44 // Subscribe to app events 46 // Subscribe to app events
45 [[NSNotificationCenter defaultCenter] addObserver:self 47 [[NSNotificationCenter defaultCenter] addObserver:self
@@ -134,6 +136,45 @@ static SDImageCache *instance; @@ -134,6 +136,45 @@ static SDImageCache *instance;
134 [fileManager release]; 136 [fileManager release];
135 } 137 }
136 138
  139 +- (void)notifyDelegate:(NSDictionary *)arguments
  140 +{
  141 + NSString *key = [arguments objectForKey:@"key"];
  142 + id <SDImageCacheDelegate> delegate = [arguments objectForKey:@"delegate"];
  143 + NSDictionary *info = [arguments objectForKey:@"userInfo"];
  144 + UIImage *image = [arguments objectForKey:@"image"];
  145 +
  146 + if (image)
  147 + {
  148 + [memCache setObject:image forKey:key];
  149 +
  150 + if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:)])
  151 + {
  152 + [delegate imageCache:self didFindImage:image forKey:key userInfo:info];
  153 + }
  154 + }
  155 + else
  156 + {
  157 + if ([delegate respondsToSelector:@selector(imageCache:didNotFindImageForKey:userInfo:)])
  158 + {
  159 + [delegate imageCache:self didNotFindImageForKey:key userInfo:info];
  160 + }
  161 + }
  162 +}
  163 +
  164 +- (void)queryDiskCacheOperation:(NSDictionary *)arguments
  165 +{
  166 + NSString *key = [arguments objectForKey:@"key"];
  167 + NSMutableDictionary *mutableArguments = [[arguments mutableCopy] autorelease];
  168 +
  169 + UIImage *image = [[[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]] autorelease];
  170 + if (image)
  171 + {
  172 + [mutableArguments setObject:image forKey:@"image"];
  173 + }
  174 +
  175 + [self performSelectorOnMainThread:@selector(notifyDelegate:) withObject:mutableArguments waitUntilDone:NO];
  176 +}
  177 +
137 #pragma mark ImageCache 178 #pragma mark ImageCache
138 179
139 - (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk 180 - (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk
@@ -154,7 +195,6 @@ static SDImageCache *instance; @@ -154,7 +195,6 @@ static SDImageCache *instance;
154 { 195 {
155 [storeDataQueue setObject:data forKey:key]; 196 [storeDataQueue setObject:data forKey:key];
156 [cacheInQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(storeKeyToDisk:) object:key] autorelease]]; 197 [cacheInQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(storeKeyToDisk:) object:key] autorelease]];
157 -  
158 } 198 }
159 } 199 }
160 200
@@ -185,17 +225,54 @@ static SDImageCache *instance; @@ -185,17 +225,54 @@ static SDImageCache *instance;
185 225
186 if (!image && fromDisk) 226 if (!image && fromDisk)
187 { 227 {
188 - image = [[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]];  
189 - if (image != nil) 228 + image = [[[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]] autorelease];
  229 + if (image)
190 { 230 {
191 [memCache setObject:image forKey:key]; 231 [memCache setObject:image forKey:key];
192 - [image autorelease];  
193 } 232 }
194 } 233 }
195 234
196 return image; 235 return image;
197 } 236 }
198 237
  238 +- (void)queryDiskCacheForKey:(NSString *)key delegate:(id <SDImageCacheDelegate>)delegate userInfo:(NSDictionary *)info
  239 +{
  240 + if (!delegate)
  241 + {
  242 + return;
  243 + }
  244 +
  245 + if (!key)
  246 + {
  247 + if ([delegate respondsToSelector:@selector(imageCache:didNotFindImageForKey:userInfo:)])
  248 + {
  249 + [delegate imageCache:self didNotFindImageForKey:key userInfo:info];
  250 + }
  251 + return;
  252 + }
  253 +
  254 + // First check the in-memory cache...
  255 + UIImage *image = [memCache objectForKey:key];
  256 + if (image)
  257 + {
  258 + // ...notify delegate immediately, no need to go async
  259 + if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:)])
  260 + {
  261 + [delegate imageCache:self didFindImage:image forKey:key userInfo:info];
  262 + }
  263 + return;
  264 + }
  265 +
  266 + NSMutableDictionary *arguments = [NSMutableDictionary dictionaryWithCapacity:3];
  267 + [arguments setObject:key forKey:@"key"];
  268 + [arguments setObject:delegate forKey:@"delegate"];
  269 + if (info)
  270 + {
  271 + [arguments setObject:info forKey:@"userInfo"];
  272 + }
  273 + [cacheOutQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(queryDiskCacheOperation:) object:arguments] autorelease]];
  274 +}
  275 +
199 - (void)removeImageForKey:(NSString *)key 276 - (void)removeImageForKey:(NSString *)key
200 { 277 {
201 if (key == nil) 278 if (key == nil)
  1 +//
  2 +// SDImageCacheDelegate.h
  3 +// Dailymotion
  4 +//
  5 +// Created by Olivier Poitrey on 16/09/10.
  6 +// Copyright 2010 Dailymotion. All rights reserved.
  7 +//
  8 +
  9 +#import <UIKit/UIKit.h>
  10 +
  11 +@class SDImageCache;
  12 +
  13 +@protocol SDImageCacheDelegate <NSObject>
  14 +
  15 +@optional
  16 +- (void)imageCache:(SDImageCache *)imageCache didFindImage:(UIImage *)image forKey:(NSString *)key userInfo:(NSDictionary *)info;
  17 +- (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *)key userInfo:(NSDictionary *)info;
  18 +
  19 +@end
@@ -9,8 +9,9 @@ @@ -9,8 +9,9 @@
9 #import <UIKit/UIKit.h> 9 #import <UIKit/UIKit.h>
10 #import "SDWebImageDownloaderDelegate.h" 10 #import "SDWebImageDownloaderDelegate.h"
11 #import "SDWebImageManagerDelegate.h" 11 #import "SDWebImageManagerDelegate.h"
  12 +#import "SDImageCacheDelegate.h"
12 13
13 -@interface SDWebImageManager : NSObject <SDWebImageDownloaderDelegate> 14 +@interface SDWebImageManager : NSObject <SDWebImageDownloaderDelegate, SDImageCacheDelegate>
14 { 15 {
15 NSMutableArray *delegates; 16 NSMutableArray *delegates;
16 NSMutableArray *downloaders; 17 NSMutableArray *downloaders;
@@ -46,6 +46,9 @@ static SDWebImageManager *instance; @@ -46,6 +46,9 @@ static SDWebImageManager *instance;
46 return instance; 46 return instance;
47 } 47 }
48 48
  49 +/**
  50 + * @deprecated
  51 + */
49 - (UIImage *)imageWithURL:(NSURL *)url 52 - (UIImage *)imageWithURL:(NSURL *)url
50 { 53 {
51 return [[SDImageCache sharedImageCache] imageFromKey:[url absoluteString]]; 54 return [[SDImageCache sharedImageCache] imageFromKey:[url absoluteString]];
@@ -53,22 +56,14 @@ static SDWebImageManager *instance; @@ -53,22 +56,14 @@ static SDWebImageManager *instance;
53 56
54 - (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)delegate 57 - (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)delegate
55 { 58 {
56 - if (url == nil || [failedURLs containsObject:url]) 59 + if (!url || !delegate || [failedURLs containsObject:url])
57 { 60 {
58 return; 61 return;
59 } 62 }
60 63
61 - // Share the same downloader for identical URLs so we don't download the same URL several times  
62 - SDWebImageDownloader *downloader = [downloaderForURL objectForKey:url];  
63 -  
64 - if (!downloader)  
65 - {  
66 - downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self];  
67 - [downloaderForURL setObject:downloader forKey:url];  
68 - }  
69 -  
70 - [delegates addObject:delegate];  
71 - [downloaders addObject:downloader]; 64 + // Check the on-disk cache async so we don't block the main thread
  65 + NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:delegate, @"delegate", url, @"url", nil];
  66 + [[SDImageCache sharedImageCache] queryDiskCacheForKey:[url absoluteString] delegate:self userInfo:info];
72 } 67 }
73 68
74 - (void)cancelForDelegate:(id<SDWebImageManagerDelegate>)delegate 69 - (void)cancelForDelegate:(id<SDWebImageManagerDelegate>)delegate
@@ -95,6 +90,37 @@ static SDWebImageManager *instance; @@ -95,6 +90,37 @@ static SDWebImageManager *instance;
95 [downloader release]; 90 [downloader release];
96 } 91 }
97 92
  93 +#pragma mark SDImageCacheDelegate
  94 +
  95 +- (void)imageCache:(SDImageCache *)imageCache didFindImage:(UIImage *)image forKey:(NSString *)key userInfo:(NSDictionary *)info
  96 +{
  97 + id<SDWebImageManagerDelegate> delegate = [info objectForKey:@"delegate"];
  98 + if ([delegate respondsToSelector:@selector(webImageManager:didFinishWithImage:)])
  99 + {
  100 + [delegate performSelector:@selector(webImageManager:didFinishWithImage:) withObject:self withObject:image];
  101 + }
  102 +}
  103 +
  104 +- (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *)key userInfo:(NSDictionary *)info
  105 +{
  106 + NSURL *url = [info objectForKey:@"url"];
  107 + id<SDWebImageManagerDelegate> delegate = [info objectForKey:@"delegate"];
  108 +
  109 + // Share the same downloader for identical URLs so we don't download the same URL several times
  110 + SDWebImageDownloader *downloader = [downloaderForURL objectForKey:url];
  111 +
  112 + if (!downloader)
  113 + {
  114 + downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self];
  115 + [downloaderForURL setObject:downloader forKey:url];
  116 + }
  117 +
  118 + [delegates addObject:delegate];
  119 + [downloaders addObject:downloader];
  120 +}
  121 +
  122 +#pragma mark SDWebImageDownloaderDelegate
  123 +
98 - (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image 124 - (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image
99 { 125 {
100 [downloader retain]; 126 [downloader retain];
@@ -23,31 +23,11 @@ @@ -23,31 +23,11 @@
23 // Remove in progress downloader from queue 23 // Remove in progress downloader from queue
24 [manager cancelForDelegate:self]; 24 [manager cancelForDelegate:self];
25 25
26 - UIImage *cachedImage = nil;  
27 - if (url)  
28 - {  
29 - cachedImage = [manager imageWithURL:url];  
30 - }  
31 - else  
32 - {  
33 - self.image = placeholder;  
34 - } 26 + self.image = placeholder;
35 27
36 - if (cachedImage)  
37 - {  
38 - self.image = cachedImage;  
39 - }  
40 - else 28 + if (url)
41 { 29 {
42 - if (placeholder)  
43 - {  
44 - self.image = placeholder;  
45 - }  
46 -  
47 - if (url)  
48 - {  
49 - [manager downloadWithURL:url delegate:self];  
50 - } 30 + [manager downloadWithURL:url delegate:self];
51 } 31 }
52 } 32 }
53 33