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
Showing
6 changed files
with
148 additions
and
42 deletions
@@ -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) |
SDImageCacheDelegate.h
0 → 100644
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 |
-
Please register or login to post a comment