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 @@ @@ -10,7 +10,7 @@
10 10
11 @interface SDImageCache : NSObject 11 @interface SDImageCache : NSObject
12 { 12 {
13 - NSMutableDictionary *memCache; 13 + NSMutableDictionary *memCache, *storeDataQueue;
14 NSString *diskCachePath; 14 NSString *diskCachePath;
15 NSOperationQueue *cacheInQueue; 15 NSOperationQueue *cacheInQueue;
16 } 16 }
@@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
18 + (SDImageCache *)sharedImageCache; 18 + (SDImageCache *)sharedImageCache;
19 - (void)storeImage:(UIImage *)image forKey:(NSString *)key; 19 - (void)storeImage:(UIImage *)image forKey:(NSString *)key;
20 - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk; 20 - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;
  21 +- (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk;
21 - (UIImage *)imageFromKey:(NSString *)key; 22 - (UIImage *)imageFromKey:(NSString *)key;
22 - (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk; 23 - (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk;
23 - (void)removeImageForKey:(NSString *)key; 24 - (void)removeImageForKey:(NSString *)key;
@@ -25,6 +25,7 @@ static SDImageCache *instance; @@ -25,6 +25,7 @@ static SDImageCache *instance;
25 memCache = [[NSMutableDictionary alloc] init]; 25 memCache = [[NSMutableDictionary alloc] init];
26 26
27 // Init the disk cache 27 // Init the disk cache
  28 + storeDataQueue = [[NSMutableDictionary alloc] init];
28 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 29 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
29 diskCachePath = [[[paths objectAtIndex:0] stringByAppendingPathComponent:@"ImageCache"] retain]; 30 diskCachePath = [[[paths objectAtIndex:0] stringByAppendingPathComponent:@"ImageCache"] retain];
30 31
@@ -72,6 +73,7 @@ static SDImageCache *instance; @@ -72,6 +73,7 @@ static SDImageCache *instance;
72 [memCache release], memCache = nil; 73 [memCache release], memCache = nil;
73 [diskCachePath release], diskCachePath = nil; 74 [diskCachePath release], diskCachePath = nil;
74 [cacheInQueue release], cacheInQueue = nil; 75 [cacheInQueue release], cacheInQueue = nil;
  76 + [storeDataQueue release], storeDataQueue = nil;
75 77
76 [[NSNotificationCenter defaultCenter] removeObserver:self]; 78 [[NSNotificationCenter defaultCenter] removeObserver:self];
77 79
@@ -105,25 +107,43 @@ static SDImageCache *instance; @@ -105,25 +107,43 @@ static SDImageCache *instance;
105 107
106 - (void)storeKeyToDisk:(NSString *)key 108 - (void)storeKeyToDisk:(NSString *)key
107 { 109 {
108 - UIImage *image = [[self imageFromKey:key fromDisk:YES] retain]; // be thread safe with no lock 110 + // Can't use defaultManager another thread
  111 + NSFileManager *fileManager = [[NSFileManager alloc] init];
109 112
110 - if (image != nil) 113 + NSData *data = [storeDataQueue objectForKey:key];
  114 + if (data)
  115 + {
  116 + [fileManager createFileAtPath:[self cachePathForKey:key] contents:data attributes:nil];
  117 + @synchronized(storeDataQueue)
  118 + {
  119 + [storeDataQueue removeObjectForKey:key];
  120 + }
  121 + }
  122 + else
  123 + {
  124 + // If no data representation given, convert the UIImage in JPEG and store it
  125 + // This trick is more CPU/memory intensive and doesn't preserve alpha channel
  126 + UIImage *image = [[self imageFromKey:key fromDisk:YES] retain]; // be thread safe with no lock
  127 + if (image)
111 { 128 {
112 - [[NSFileManager defaultManager] createFileAtPath:[self cachePathForKey:key] contents:UIImageJPEGRepresentation(image, (CGFloat)1.0) attributes:nil]; 129 + [fileManager createFileAtPath:[self cachePathForKey:key] contents:UIImageJPEGRepresentation(image, (CGFloat)1.0) attributes:nil];
113 [image release]; 130 [image release];
114 } 131 }
  132 + }
  133 +
  134 + [fileManager release];
115 } 135 }
116 136
117 #pragma mark ImageCache 137 #pragma mark ImageCache
118 138
119 -- (void)storeImage:(UIImage *)image forKey:(NSString *)key 139 +- (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk
120 { 140 {
121 - [self storeImage:image forKey:key toDisk:YES];  
122 -} 141 + if (!image || !key)
  142 + {
  143 + return;
  144 + }
123 145
124 -- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk  
125 -{  
126 - if (image == nil || key == nil) 146 + if (toDisk && !data)
127 { 147 {
128 return; 148 return;
129 } 149 }
@@ -132,10 +152,23 @@ static SDImageCache *instance; @@ -132,10 +152,23 @@ static SDImageCache *instance;
132 152
133 if (toDisk) 153 if (toDisk)
134 { 154 {
  155 + [storeDataQueue setObject:data forKey:key];
135 [cacheInQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(storeKeyToDisk:) object:key] autorelease]]; 156 [cacheInQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(storeKeyToDisk:) object:key] autorelease]];
  157 +
136 } 158 }
137 } 159 }
138 160
  161 +- (void)storeImage:(UIImage *)image forKey:(NSString *)key
  162 +{
  163 + [self storeImage:image imageData:nil forKey:key toDisk:YES];
  164 +}
  165 +
  166 +- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk
  167 +{
  168 + [self storeImage:image imageData:nil forKey:key toDisk:toDisk];
  169 +}
  170 +
  171 +
139 - (UIImage *)imageFromKey:(NSString *)key 172 - (UIImage *)imageFromKey:(NSString *)key
140 { 173 {
141 return [self imageFromKey:key fromDisk:YES]; 174 return [self imageFromKey:key fromDisk:YES];
@@ -152,7 +185,7 @@ static SDImageCache *instance; @@ -152,7 +185,7 @@ static SDImageCache *instance;
152 185
153 if (!image && fromDisk) 186 if (!image && fromDisk)
154 { 187 {
155 - image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:[self cachePathForKey:key]]]; 188 + image = [[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]];
156 if (image != nil) 189 if (image != nil)
157 { 190 {
158 [memCache setObject:image forKey:key]; 191 [memCache setObject:image forKey:key];
@@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
20 20
21 @property (nonatomic, retain) NSURL *url; 21 @property (nonatomic, retain) NSURL *url;
22 @property (nonatomic, assign) id<SDWebImageDownloaderDelegate> delegate; 22 @property (nonatomic, assign) id<SDWebImageDownloaderDelegate> delegate;
  23 +@property (nonatomic, retain) NSMutableData *imageData;
23 24
24 + (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate; 25 + (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate;
25 - (void)start; 26 - (void)start;
@@ -10,7 +10,6 @@ @@ -10,7 +10,6 @@
10 10
11 @interface SDWebImageDownloader () 11 @interface SDWebImageDownloader ()
12 @property (nonatomic, retain) NSURLConnection *connection; 12 @property (nonatomic, retain) NSURLConnection *connection;
13 -@property (nonatomic, retain) NSMutableData *imageData;  
14 @end 13 @end
15 14
16 @implementation SDWebImageDownloader 15 @implementation SDWebImageDownloader
@@ -73,16 +72,19 @@ @@ -73,16 +72,19 @@
73 72
74 - (void)connectionDidFinishLoading:(NSURLConnection *)aConnection 73 - (void)connectionDidFinishLoading:(NSURLConnection *)aConnection
75 { 74 {
76 - UIImage *image = [[UIImage alloc] initWithData:imageData];  
77 - self.imageData = nil;  
78 self.connection = nil; 75 self.connection = nil;
79 76
80 - if ([delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:)]) 77 + if ([delegate respondsToSelector:@selector(imageDownloaderDidFinish:)])
81 { 78 {
82 - [delegate performSelector:@selector(imageDownloader:didFinishWithImage:) withObject:self withObject:image]; 79 + [delegate performSelector:@selector(imageDownloaderDidFinish:) withObject:self];
83 } 80 }
84 81
  82 + if ([delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:)])
  83 + {
  84 + UIImage *image = [[UIImage alloc] initWithData:imageData];
  85 + [delegate performSelector:@selector(imageDownloader:didFinishWithImage:) withObject:self withObject:image];
85 [image release]; 86 [image release];
  87 + }
86 } 88 }
87 89
88 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 90 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
@@ -12,6 +12,7 @@ @@ -12,6 +12,7 @@
12 12
13 @optional 13 @optional
14 14
  15 +- (void)imageDownloaderDidFinish:(SDWebImageDownloader *)downloader;
15 - (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image; 16 - (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image;
16 - (void)imageDownloader:(SDWebImageDownloader *)downloader didFailWithError:(NSError *)error; 17 - (void)imageDownloader:(SDWebImageDownloader *)downloader didFailWithError:(NSError *)error;
17 18
@@ -120,7 +120,10 @@ static SDWebImageManager *instance; @@ -120,7 +120,10 @@ static SDWebImageManager *instance;
120 if (image) 120 if (image)
121 { 121 {
122 // Store the image in the cache 122 // Store the image in the cache
123 - [[SDImageCache sharedImageCache] storeImage:image forKey:[downloader.url absoluteString]]; 123 + [[SDImageCache sharedImageCache] storeImage:image
  124 + imageData:downloader.imageData
  125 + forKey:[downloader.url absoluteString]
  126 + toDisk:YES];
124 } 127 }
125 else 128 else
126 { 129 {