/* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageCache.h" #import static NSInteger cacheMaxCacheAge = 60*60*24*7; // 1 week static SDImageCache *instance; @implementation SDImageCache #pragma mark NSObject - (id)init { if ((self = [super init])) { // Init the memory cache memCache = [[NSMutableDictionary alloc] init]; // Init the disk cache NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); diskCachePath = [[[paths objectAtIndex:0] stringByAppendingPathComponent:@"ImageCache"] retain]; if (![[NSFileManager defaultManager] fileExistsAtPath:diskCachePath]) { [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL]; } // Init the operation queue cacheInQueue = [[NSOperationQueue alloc] init]; cacheInQueue.maxConcurrentOperationCount = 2; // Subscribe to app events [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clearMemory) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanDisk) name:UIApplicationWillTerminateNotification object:nil]; #ifdef __IPHONE_4_0 UIDevice *device = [UIDevice currentDevice]; if ([device respondsToSelector:@selector(isMultitaskingSupported)] && device.multitaskingSupported) { // When in background, clean memory in order to have less chance to be killed [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clearMemory) name:UIApplicationDidEnterBackgroundNotification object:nil]; } #endif } return self; } - (void)dealloc { [memCache release], memCache = nil; [diskCachePath release], diskCachePath = nil; [cacheInQueue release], cacheInQueue = nil; [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } #pragma mark SDImageCache (class methods) + (SDImageCache *)sharedImageCache { if (instance == nil) { instance = [[SDImageCache alloc] init]; } return instance; } #pragma mark SDImageCache (private) - (NSString *)cachePathForKey:(NSString *)key { const char *str = [key UTF8String]; unsigned char r[CC_MD5_DIGEST_LENGTH]; CC_MD5(str, 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 [diskCachePath stringByAppendingPathComponent:filename]; } - (void)storeKeyToDisk:(NSString *)key { UIImage *image = [[self imageFromKey:key fromDisk:YES] retain]; // be thread safe with no lock if (image != nil) { [[NSFileManager defaultManager] createFileAtPath:[self cachePathForKey:key] contents:UIImageJPEGRepresentation(image, (CGFloat)1.0) attributes:nil]; [image release]; } } #pragma mark ImageCache - (void)storeImage:(UIImage *)image forKey:(NSString *)key { [self storeImage:image forKey:key toDisk:YES]; } - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk { if (image == nil || key == nil) { return; } [memCache setObject:image forKey:key]; if (toDisk) { [cacheInQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(storeKeyToDisk:) object:key] autorelease]]; } } - (UIImage *)imageFromKey:(NSString *)key { return [self imageFromKey:key fromDisk:YES]; } - (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk { if (key == nil) { return nil; } UIImage *image = [memCache objectForKey:key]; if (!image && fromDisk) { image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:[self cachePathForKey:key]]]; if (image != nil) { [memCache setObject:image forKey:key]; [image autorelease]; } } return image; } - (void)removeImageForKey:(NSString *)key { if (key == nil) { return; } [memCache removeObjectForKey:key]; [[NSFileManager defaultManager] removeItemAtPath:[self cachePathForKey:key] error:nil]; } - (void)clearMemory { [cacheInQueue cancelAllOperations]; // won't be able to complete [memCache removeAllObjects]; } - (void)clearDisk { [cacheInQueue cancelAllOperations]; [[NSFileManager defaultManager] removeItemAtPath:diskCachePath error:nil]; [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL]; } - (void)cleanDisk { NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-cacheMaxCacheAge]; NSDirectoryEnumerator *fileEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:diskCachePath]; for (NSString *fileName in fileEnumerator) { NSString *filePath = [diskCachePath stringByAppendingPathComponent:fileName]; NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; if ([[[attrs fileModificationDate] laterDate:expirationDate] isEqualToDate:expirationDate]) { [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; } } } @end