diff --git a/DMImageCache.h b/DMImageCache.h index 557a062b..d2be8645 100644 --- a/DMImageCache.h +++ b/DMImageCache.h @@ -10,8 +10,9 @@ @interface DMImageCache : NSObject { - NSMutableDictionary *cache; + NSMutableDictionary *memCache; NSString *diskCachePath; + NSOperationQueue *cacheInQueue; } + (DMImageCache *)sharedImageCache; diff --git a/DMImageCache.m b/DMImageCache.m index 652f4ac3..ad40a015 100644 --- a/DMImageCache.m +++ b/DMImageCache.m @@ -9,7 +9,7 @@ #import "DMImageCache.h" #import -static NSInteger kMaxCacheAge = 60*60*24*7; // 1 week +static NSInteger cacheMaxCacheAge = 60*60*24*7; // 1 week static DMImageCache *instance; @@ -21,19 +21,10 @@ static DMImageCache *instance; { if (self = [super init]) { - cache = [[NSMutableDictionary alloc] init]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(didReceiveMemoryWarning:) - name:UIApplicationDidReceiveMemoryWarningNotification - object:nil]; + // Init the memory cache + memCache = [[NSMutableDictionary alloc] init]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(willTerminate) - name:UIApplicationWillTerminateNotification - object:nil]; - - // Init the cache + // Init the disk cache NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); diskCachePath = [[[paths objectAtIndex:0] stringByAppendingPathComponent:@"ImageCache"] retain]; @@ -41,6 +32,21 @@ static DMImageCache *instance; { [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath attributes:nil]; } + + // Init the operation queue + cacheInQueue = [[NSOperationQueue alloc] init]; + cacheInQueue.maxConcurrentOperationCount = 2; + + // Subscribe to app events + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didReceiveMemoryWarning:) + name:UIApplicationDidReceiveMemoryWarningNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(willTerminate) + name:UIApplicationWillTerminateNotification + object:nil]; } return self; @@ -48,7 +54,9 @@ static DMImageCache *instance; - (void)dealloc { - [cache release]; + [memCache release]; + [diskCachePath release]; + [cacheInQueue release]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification @@ -71,6 +79,18 @@ static DMImageCache *instance; [self cleanDisk]; } +#pragma mark ImageCache (class methods) + ++ (DMImageCache *)sharedImageCache +{ + if (instance == nil) + { + instance = [[DMImageCache alloc] init]; + } + + return instance; +} + #pragma mark ImageCache (private) - (NSString *)cachePathForKey:(NSString *)key @@ -84,18 +104,19 @@ static DMImageCache *instance; return [diskCachePath stringByAppendingPathComponent:filename]; } -#pragma mark ImageCache - -+ (DMImageCache *)sharedImageCache +- (void)storeKeyToDisk:(NSString *)key { - if (instance == nil) - { - instance = [[DMImageCache alloc] init]; - } + UIImage *image = [[self imageFromKey:key fromDisk:YES] retain]; // be thread safe with no lock - return instance; + if (image != nil) + { + [[NSFileManager defaultManager] createFileAtPath:[self cachePathForKey:key] contents:UIImageJPEGRepresentation(image, 1.0) attributes:nil]; + [image release]; + } } +#pragma mark ImageCache + - (void)storeImage:(UIImage *)image forKey:(NSString *)key { [self storeImage:image forKey:key toDisk:YES]; @@ -108,11 +129,11 @@ static DMImageCache *instance; return; } - [cache setObject:image forKey:key]; + [memCache setObject:image forKey:key]; if (toDisk) - { - [[NSFileManager defaultManager] createFileAtPath:[self cachePathForKey:key] contents:UIImageJPEGRepresentation(image, 1.0) attributes:nil]; + { + [cacheInQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(storeKeyToDisk:) object:key] autorelease]]; } } @@ -123,14 +144,14 @@ static DMImageCache *instance; - (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk { - UIImage *image = [cache objectForKey:key]; + UIImage *image = [memCache objectForKey:key]; if (!image && fromDisk) { image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:[self cachePathForKey:key]]]; if (image != nil) { - [cache setObject:image forKey:key]; + [memCache setObject:image forKey:key]; [image release]; } } @@ -140,24 +161,26 @@ static DMImageCache *instance; - (void)removeImageForKey:(NSString *)key { - [cache removeObjectForKey:key]; + [memCache removeObjectForKey:key]; [[NSFileManager defaultManager] removeItemAtPath:[self cachePathForKey:key] error:nil]; } - (void)clearMemory { - [cache removeAllObjects]; + [cacheInQueue cancelAllOperations]; // won't be able to complete + [memCache removeAllObjects]; } - (void)clearDisk { + [cacheInQueue cancelAllOperations]; [[NSFileManager defaultManager] removeItemAtPath:diskCachePath error:nil]; [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath attributes:nil]; } - (void)cleanDisk { - NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-kMaxCacheAge]; + NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-cacheMaxCacheAge]; NSDirectoryEnumerator *fileEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:diskCachePath]; for (NSString *fileName in fileEnumerator) { diff --git a/DMWebImageDownloader.h b/DMWebImageDownloader.h new file mode 100644 index 00000000..f8504cbd --- /dev/null +++ b/DMWebImageDownloader.h @@ -0,0 +1,26 @@ +/* + * This file is part of the DMWebImage package. + * (c) Dailymotion - Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import + + +@interface DMWebImageDownloader : NSOperation +{ + NSURL *url; + id target; + SEL action; +} + +@property (retain) NSURL *url; +@property (assign) id target; +@property (assign) SEL action; + ++ (id)downloaderWithURL:(NSURL *)url target:(id)target action:(SEL)action; ++ (void)setMaxConcurrentDownloads:(NSUInteger)max; + +@end diff --git a/DMWebImageDownloader.m b/DMWebImageDownloader.m new file mode 100644 index 00000000..74a03de9 --- /dev/null +++ b/DMWebImageDownloader.m @@ -0,0 +1,68 @@ +/* + * This file is part of the DMWebImage package. + * (c) Dailymotion - Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "DMWebImageDownloader.h" +#import "DMImageCache.h" + +static NSOperationQueue *queue; + +@implementation DMWebImageDownloader + +@synthesize url, target, action; + +- (void)dealloc +{ + [url release]; + [super dealloc]; +} + ++ (id)downloaderWithURL:(NSURL *)url target:(id)target action:(SEL)action +{ + DMWebImageDownloader *downloader = [[[DMWebImageDownloader alloc] init] autorelease]; + downloader.url = url; + downloader.target = target; + downloader.action = action; + + if (queue == nil) + { + queue = [[NSOperationQueue alloc] init]; + queue.maxConcurrentOperationCount = 8; + } + + [queue addOperation:downloader]; + + return downloader; +} + ++ (void)setMaxConcurrentDownloads:(NSUInteger)max +{ + if (queue == nil) + { + queue = [[NSOperationQueue alloc] init]; + } + + queue.maxConcurrentOperationCount = max; +} + +- (void)main +{ + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]; + + if (!self.isCancelled) + { + [target performSelector:action withObject:image]; + } + + [[DMImageCache sharedImageCache] storeImage:image forKey:[url absoluteString]]; + + [pool release]; +} + +@end diff --git a/DMWebImageView.h b/DMWebImageView.h index e0c3837b..4bccb493 100644 --- a/DMWebImageView.h +++ b/DMWebImageView.h @@ -8,28 +8,15 @@ #import -@class DMWebImageDownloadOperation; +@class DMWebImageDownloader; @interface DMWebImageView : UIImageView { UIImage *placeHolderImage; - DMWebImageDownloadOperation *currentOperation; + DMWebImageDownloader *currentOperation; } - (void)setImageWithURL:(NSURL *)url; - (void)downloadFinishedWithImage:(UIImage *)image; -@end - -@interface DMWebImageDownloadOperation : NSOperation -{ - NSURL *url; - DMWebImageView *delegate; -} - -@property (retain) NSURL *url; -@property (assign) DMWebImageView *delegate; - -- (id)initWithURL:(NSURL *)url delegate:(DMWebImageView *)delegate; - -@end +@end \ No newline at end of file diff --git a/DMWebImageView.m b/DMWebImageView.m index e4793b28..e823ebac 100644 --- a/DMWebImageView.m +++ b/DMWebImageView.m @@ -8,9 +8,7 @@ #import "DMWebImageView.h" #import "DMImageCache.h" - -static NSOperationQueue *downloadQueue; -static NSOperationQueue *cacheInQueue; +#import "DMWebImageDownloader.h" @implementation DMWebImageView @@ -49,15 +47,8 @@ static NSOperationQueue *cacheInQueue; self.image = cachedImage; } else - { - if (downloadQueue == nil) - { - downloadQueue = [[NSOperationQueue alloc] init]; - [downloadQueue setMaxConcurrentOperationCount:8]; - } - - currentOperation = [[DMWebImageDownloadOperation alloc] initWithURL:url delegate:self]; - [downloadQueue addOperation:currentOperation]; + { + currentOperation = [[DMWebImageDownloader downloaderWithURL:url target:self action:@selector(downloadFinishedWithImage:)] retain]; } } @@ -69,70 +60,3 @@ static NSOperationQueue *cacheInQueue; } @end - -@implementation DMWebImageDownloadOperation - -@synthesize url, delegate; - -- (void)dealloc -{ - [url release]; - [super dealloc]; -} - - -- (id)initWithURL:(NSURL *)anUrl delegate:(DMWebImageView *)aDelegate -{ - if (self = [super init]) - { - self.url = anUrl; - self.delegate = aDelegate; - } - - return self; -} - -- (void)main -{ - if (self.isCancelled) - { - return; - } - - NSData *data = [[NSData alloc] initWithContentsOfURL:url]; - UIImage *image = [[UIImage alloc] initWithData:data]; - [data release]; - - if (!self.isCancelled) - { - [delegate performSelectorOnMainThread:@selector(downloadFinishedWithImage:) withObject:image waitUntilDone:YES]; - } - - if (cacheInQueue == nil) - { - cacheInQueue = [[NSOperationQueue alloc] init]; - [cacheInQueue setMaxConcurrentOperationCount:2]; - } - - NSString *cacheKey = [url absoluteString]; - - DMImageCache *imageCache = [DMImageCache sharedImageCache]; - - // Store image in memory cache NOW, no need to wait for the cache-in operation queue completion - [imageCache storeImage:image forKey:cacheKey toDisk:NO]; - - // Perform the cache-in in another operation queue in order to not block a download operation slot - NSInvocation *cacheInInvocation = [NSInvocation invocationWithMethodSignature:[[imageCache class] instanceMethodSignatureForSelector:@selector(storeImage:forKey:)]]; - [cacheInInvocation setTarget:imageCache]; - [cacheInInvocation setSelector:@selector(storeImage:forKey:)]; - [cacheInInvocation setArgument:&image atIndex:2]; - [cacheInInvocation setArgument:&cacheKey atIndex:3]; - [cacheInInvocation retainArguments]; - NSInvocationOperation *cacheInOperation = [[NSInvocationOperation alloc] initWithInvocation:cacheInInvocation]; - [cacheInQueue addOperation:cacheInOperation]; - [cacheInOperation release]; - - [image release]; -} - -@end