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 @@
*/
#import <Foundation/Foundation.h>
#import "SDImageCacheDelegate.h"
@interface SDImageCache : NSObject
{
NSMutableDictionary *memCache, *storeDataQueue;
NSString *diskCachePath;
NSOperationQueue *cacheInQueue;
NSOperationQueue *cacheInQueue, *cacheOutQueue;
}
+ (SDImageCache *)sharedImageCache;
... ... @@ -21,6 +22,8 @@
- (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk;
- (UIImage *)imageFromKey:(NSString *)key;
- (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk;
- (void)queryDiskCacheForKey:(NSString *)key delegate:(id <SDImageCacheDelegate>)delegate userInfo:(NSDictionary *)info;
- (void)removeImageForKey:(NSString *)key;
- (void)clearMemory;
- (void)clearDisk;
... ...
... ... @@ -39,7 +39,9 @@ static SDImageCache *instance;
// Init the operation queue
cacheInQueue = [[NSOperationQueue alloc] init];
cacheInQueue.maxConcurrentOperationCount = 2;
cacheInQueue.maxConcurrentOperationCount = 1;
cacheOutQueue = [[NSOperationQueue alloc] init];
cacheOutQueue.maxConcurrentOperationCount = 1;
// Subscribe to app events
[[NSNotificationCenter defaultCenter] addObserver:self
... ... @@ -134,6 +136,45 @@ static SDImageCache *instance;
[fileManager release];
}
- (void)notifyDelegate:(NSDictionary *)arguments
{
NSString *key = [arguments objectForKey:@"key"];
id <SDImageCacheDelegate> delegate = [arguments objectForKey:@"delegate"];
NSDictionary *info = [arguments objectForKey:@"userInfo"];
UIImage *image = [arguments objectForKey:@"image"];
if (image)
{
[memCache setObject:image forKey:key];
if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:)])
{
[delegate imageCache:self didFindImage:image forKey:key userInfo:info];
}
}
else
{
if ([delegate respondsToSelector:@selector(imageCache:didNotFindImageForKey:userInfo:)])
{
[delegate imageCache:self didNotFindImageForKey:key userInfo:info];
}
}
}
- (void)queryDiskCacheOperation:(NSDictionary *)arguments
{
NSString *key = [arguments objectForKey:@"key"];
NSMutableDictionary *mutableArguments = [[arguments mutableCopy] autorelease];
UIImage *image = [[[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]] autorelease];
if (image)
{
[mutableArguments setObject:image forKey:@"image"];
}
[self performSelectorOnMainThread:@selector(notifyDelegate:) withObject:mutableArguments waitUntilDone:NO];
}
#pragma mark ImageCache
- (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk
... ... @@ -154,7 +195,6 @@ static SDImageCache *instance;
{
[storeDataQueue setObject:data forKey:key];
[cacheInQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(storeKeyToDisk:) object:key] autorelease]];
}
}
... ... @@ -185,17 +225,54 @@ static SDImageCache *instance;
if (!image && fromDisk)
{
image = [[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]];
if (image != nil)
image = [[[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]] autorelease];
if (image)
{
[memCache setObject:image forKey:key];
[image autorelease];
}
}
return image;
}
- (void)queryDiskCacheForKey:(NSString *)key delegate:(id <SDImageCacheDelegate>)delegate userInfo:(NSDictionary *)info
{
if (!delegate)
{
return;
}
if (!key)
{
if ([delegate respondsToSelector:@selector(imageCache:didNotFindImageForKey:userInfo:)])
{
[delegate imageCache:self didNotFindImageForKey:key userInfo:info];
}
return;
}
// First check the in-memory cache...
UIImage *image = [memCache objectForKey:key];
if (image)
{
// ...notify delegate immediately, no need to go async
if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:)])
{
[delegate imageCache:self didFindImage:image forKey:key userInfo:info];
}
return;
}
NSMutableDictionary *arguments = [NSMutableDictionary dictionaryWithCapacity:3];
[arguments setObject:key forKey:@"key"];
[arguments setObject:delegate forKey:@"delegate"];
if (info)
{
[arguments setObject:info forKey:@"userInfo"];
}
[cacheOutQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(queryDiskCacheOperation:) object:arguments] autorelease]];
}
- (void)removeImageForKey:(NSString *)key
{
if (key == nil)
... ...
//
// SDImageCacheDelegate.h
// Dailymotion
//
// Created by Olivier Poitrey on 16/09/10.
// Copyright 2010 Dailymotion. All rights reserved.
//
#import <UIKit/UIKit.h>
@class SDImageCache;
@protocol SDImageCacheDelegate <NSObject>
@optional
- (void)imageCache:(SDImageCache *)imageCache didFindImage:(UIImage *)image forKey:(NSString *)key userInfo:(NSDictionary *)info;
- (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *)key userInfo:(NSDictionary *)info;
@end
... ...
... ... @@ -9,8 +9,9 @@
#import <UIKit/UIKit.h>
#import "SDWebImageDownloaderDelegate.h"
#import "SDWebImageManagerDelegate.h"
#import "SDImageCacheDelegate.h"
@interface SDWebImageManager : NSObject <SDWebImageDownloaderDelegate>
@interface SDWebImageManager : NSObject <SDWebImageDownloaderDelegate, SDImageCacheDelegate>
{
NSMutableArray *delegates;
NSMutableArray *downloaders;
... ...
... ... @@ -46,6 +46,9 @@ static SDWebImageManager *instance;
return instance;
}
/**
* @deprecated
*/
- (UIImage *)imageWithURL:(NSURL *)url
{
return [[SDImageCache sharedImageCache] imageFromKey:[url absoluteString]];
... ... @@ -53,22 +56,14 @@ static SDWebImageManager *instance;
- (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)delegate
{
if (url == nil || [failedURLs containsObject:url])
if (!url || !delegate || [failedURLs containsObject:url])
{
return;
}
// Share the same downloader for identical URLs so we don't download the same URL several times
SDWebImageDownloader *downloader = [downloaderForURL objectForKey:url];
if (!downloader)
{
downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self];
[downloaderForURL setObject:downloader forKey:url];
}
[delegates addObject:delegate];
[downloaders addObject:downloader];
// Check the on-disk cache async so we don't block the main thread
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:delegate, @"delegate", url, @"url", nil];
[[SDImageCache sharedImageCache] queryDiskCacheForKey:[url absoluteString] delegate:self userInfo:info];
}
- (void)cancelForDelegate:(id<SDWebImageManagerDelegate>)delegate
... ... @@ -95,6 +90,37 @@ static SDWebImageManager *instance;
[downloader release];
}
#pragma mark SDImageCacheDelegate
- (void)imageCache:(SDImageCache *)imageCache didFindImage:(UIImage *)image forKey:(NSString *)key userInfo:(NSDictionary *)info
{
id<SDWebImageManagerDelegate> delegate = [info objectForKey:@"delegate"];
if ([delegate respondsToSelector:@selector(webImageManager:didFinishWithImage:)])
{
[delegate performSelector:@selector(webImageManager:didFinishWithImage:) withObject:self withObject:image];
}
}
- (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *)key userInfo:(NSDictionary *)info
{
NSURL *url = [info objectForKey:@"url"];
id<SDWebImageManagerDelegate> delegate = [info objectForKey:@"delegate"];
// Share the same downloader for identical URLs so we don't download the same URL several times
SDWebImageDownloader *downloader = [downloaderForURL objectForKey:url];
if (!downloader)
{
downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self];
[downloaderForURL setObject:downloader forKey:url];
}
[delegates addObject:delegate];
[downloaders addObject:downloader];
}
#pragma mark SDWebImageDownloaderDelegate
- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image
{
[downloader retain];
... ...
... ... @@ -23,31 +23,11 @@
// Remove in progress downloader from queue
[manager cancelForDelegate:self];
UIImage *cachedImage = nil;
if (url)
{
cachedImage = [manager imageWithURL:url];
}
else
{
self.image = placeholder;
}
self.image = placeholder;
if (cachedImage)
{
self.image = cachedImage;
}
else
if (url)
{
if (placeholder)
{
self.image = placeholder;
}
if (url)
{
[manager downloadWithURL:url delegate:self];
}
[manager downloadWithURL:url delegate:self];
}
}
... ...