Authored by Olivier Poitrey

Do not convert images to JPEG when stored to disk for caching

This saves CPU and memory in all cases and alpha channel / image clearness if orignal format was PNG or GIF.
... ... @@ -10,7 +10,7 @@
@interface SDImageCache : NSObject
{
NSMutableDictionary *memCache;
NSMutableDictionary *memCache, *storeDataQueue;
NSString *diskCachePath;
NSOperationQueue *cacheInQueue;
}
... ... @@ -18,6 +18,7 @@
+ (SDImageCache *)sharedImageCache;
- (void)storeImage:(UIImage *)image forKey:(NSString *)key;
- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;
- (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)removeImageForKey:(NSString *)key;
... ...
... ... @@ -25,6 +25,7 @@ static SDImageCache *instance;
memCache = [[NSMutableDictionary alloc] init];
// Init the disk cache
storeDataQueue = [[NSMutableDictionary alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
diskCachePath = [[[paths objectAtIndex:0] stringByAppendingPathComponent:@"ImageCache"] retain];
... ... @@ -72,6 +73,7 @@ static SDImageCache *instance;
[memCache release], memCache = nil;
[diskCachePath release], diskCachePath = nil;
[cacheInQueue release], cacheInQueue = nil;
[storeDataQueue release], storeDataQueue = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
... ... @@ -105,25 +107,43 @@ static SDImageCache *instance;
- (void)storeKeyToDisk:(NSString *)key
{
UIImage *image = [[self imageFromKey:key fromDisk:YES] retain]; // be thread safe with no lock
// Can't use defaultManager another thread
NSFileManager *fileManager = [[NSFileManager alloc] init];
if (image != nil)
NSData *data = [storeDataQueue objectForKey:key];
if (data)
{
[fileManager createFileAtPath:[self cachePathForKey:key] contents:data attributes:nil];
@synchronized(storeDataQueue)
{
[storeDataQueue removeObjectForKey:key];
}
}
else
{
// If no data representation given, convert the UIImage in JPEG and store it
// This trick is more CPU/memory intensive and doesn't preserve alpha channel
UIImage *image = [[self imageFromKey:key fromDisk:YES] retain]; // be thread safe with no lock
if (image)
{
[[NSFileManager defaultManager] createFileAtPath:[self cachePathForKey:key] contents:UIImageJPEGRepresentation(image, (CGFloat)1.0) attributes:nil];
[fileManager createFileAtPath:[self cachePathForKey:key] contents:UIImageJPEGRepresentation(image, (CGFloat)1.0) attributes:nil];
[image release];
}
}
[fileManager release];
}
#pragma mark ImageCache
- (void)storeImage:(UIImage *)image forKey:(NSString *)key
- (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk
{
[self storeImage:image forKey:key toDisk:YES];
}
if (!image || !key)
{
return;
}
- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk
{
if (image == nil || key == nil)
if (toDisk && !data)
{
return;
}
... ... @@ -132,10 +152,23 @@ static SDImageCache *instance;
if (toDisk)
{
[storeDataQueue setObject:data forKey:key];
[cacheInQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(storeKeyToDisk:) object:key] autorelease]];
}
}
- (void)storeImage:(UIImage *)image forKey:(NSString *)key
{
[self storeImage:image imageData:nil forKey:key toDisk:YES];
}
- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk
{
[self storeImage:image imageData:nil forKey:key toDisk:toDisk];
}
- (UIImage *)imageFromKey:(NSString *)key
{
return [self imageFromKey:key fromDisk:YES];
... ... @@ -152,7 +185,7 @@ static SDImageCache *instance;
if (!image && fromDisk)
{
image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:[self cachePathForKey:key]]];
image = [[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]];
if (image != nil)
{
[memCache setObject:image forKey:key];
... ...
... ... @@ -20,6 +20,7 @@
@property (nonatomic, retain) NSURL *url;
@property (nonatomic, assign) id<SDWebImageDownloaderDelegate> delegate;
@property (nonatomic, retain) NSMutableData *imageData;
+ (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate;
- (void)start;
... ...
... ... @@ -10,7 +10,6 @@
@interface SDWebImageDownloader ()
@property (nonatomic, retain) NSURLConnection *connection;
@property (nonatomic, retain) NSMutableData *imageData;
@end
@implementation SDWebImageDownloader
... ... @@ -73,16 +72,19 @@
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection
{
UIImage *image = [[UIImage alloc] initWithData:imageData];
self.imageData = nil;
self.connection = nil;
if ([delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:)])
if ([delegate respondsToSelector:@selector(imageDownloaderDidFinish:)])
{
[delegate performSelector:@selector(imageDownloader:didFinishWithImage:) withObject:self withObject:image];
[delegate performSelector:@selector(imageDownloaderDidFinish:) withObject:self];
}
if ([delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:)])
{
UIImage *image = [[UIImage alloc] initWithData:imageData];
[delegate performSelector:@selector(imageDownloader:didFinishWithImage:) withObject:self withObject:image];
[image release];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
... ...
... ... @@ -12,6 +12,7 @@
@optional
- (void)imageDownloaderDidFinish:(SDWebImageDownloader *)downloader;
- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image;
- (void)imageDownloader:(SDWebImageDownloader *)downloader didFailWithError:(NSError *)error;
... ...
... ... @@ -120,7 +120,10 @@ static SDWebImageManager *instance;
if (image)
{
// Store the image in the cache
[[SDImageCache sharedImageCache] storeImage:image forKey:[downloader.url absoluteString]];
[[SDImageCache sharedImageCache] storeImage:image
imageData:downloader.imageData
forKey:[downloader.url absoluteString]
toDisk:YES];
}
else
{
... ...