Showing
5 changed files
with
379 additions
and
0 deletions
DMImageCache.h
0 → 100644
1 | +// | ||
2 | +// DMImageCache.h | ||
3 | +// Dailymotion | ||
4 | +// | ||
5 | +// Created by Olivier Poitrey on 19/09/09. | ||
6 | +// Copyright 2009 Dailymotion. All rights reserved. | ||
7 | +// | ||
8 | + | ||
9 | +#import <Foundation/Foundation.h> | ||
10 | + | ||
11 | +@interface DMImageCache : NSObject | ||
12 | +{ | ||
13 | + NSMutableDictionary *cache; | ||
14 | + NSString *diskCachePath; | ||
15 | +} | ||
16 | + | ||
17 | ++ (DMImageCache *)sharedImageCache; | ||
18 | +- (void)storeImage:(UIImage *)image forKey:(NSString *)key; | ||
19 | +- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk; | ||
20 | +- (UIImage *)imageFromKey:(NSString *)key; | ||
21 | +- (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk; | ||
22 | +- (void)removeImageForKey:(NSString *)key; | ||
23 | +- (void)clearMemory; | ||
24 | +- (void)clearDisk; | ||
25 | +- (void)cleanDisk; | ||
26 | + | ||
27 | +@end |
DMImageCache.m
0 → 100644
1 | +// | ||
2 | +// DMImageCache.m | ||
3 | +// Dailymotion | ||
4 | +// | ||
5 | +// Created by Olivier Poitrey on 19/09/09. | ||
6 | +// Copyright 2009 Dailymotion. All rights reserved. | ||
7 | +// | ||
8 | + | ||
9 | +#import "DMImageCache.h" | ||
10 | +#import <CommonCrypto/CommonDigest.h> | ||
11 | + | ||
12 | +static NSInteger kMaxCacheAge = 60*60*24*7; // 1 week | ||
13 | + | ||
14 | +static DMImageCache *instance; | ||
15 | + | ||
16 | +@implementation DMImageCache | ||
17 | + | ||
18 | +#pragma mark NSObject | ||
19 | + | ||
20 | +- (id)init | ||
21 | +{ | ||
22 | + if (self = [super init]) | ||
23 | + { | ||
24 | + cache = [[NSMutableDictionary alloc] init]; | ||
25 | + | ||
26 | + [[NSNotificationCenter defaultCenter] addObserver:self | ||
27 | + selector:@selector(didReceiveMemoryWarning:) | ||
28 | + name:UIApplicationDidReceiveMemoryWarningNotification | ||
29 | + object:nil]; | ||
30 | + | ||
31 | + [[NSNotificationCenter defaultCenter] addObserver:self | ||
32 | + selector:@selector(willTerminate) | ||
33 | + name:UIApplicationWillTerminateNotification | ||
34 | + object:nil]; | ||
35 | + | ||
36 | + // Init the cache | ||
37 | + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); | ||
38 | + diskCachePath = [[[paths objectAtIndex:0] stringByAppendingPathComponent:@"ImageCache"] retain]; | ||
39 | + | ||
40 | + if (![[NSFileManager defaultManager] fileExistsAtPath:diskCachePath]) | ||
41 | + { | ||
42 | + [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath attributes:nil]; | ||
43 | + } | ||
44 | + } | ||
45 | + | ||
46 | + return self; | ||
47 | +} | ||
48 | + | ||
49 | +- (void)dealloc | ||
50 | +{ | ||
51 | + [cache release]; | ||
52 | + | ||
53 | + [[NSNotificationCenter defaultCenter] removeObserver:self | ||
54 | + name:UIApplicationDidReceiveMemoryWarningNotification | ||
55 | + object:nil]; | ||
56 | + | ||
57 | + [[NSNotificationCenter defaultCenter] removeObserver:self | ||
58 | + name:UIApplicationWillTerminateNotification | ||
59 | + object:nil]; | ||
60 | + | ||
61 | + [super dealloc]; | ||
62 | +} | ||
63 | + | ||
64 | +- (void)didReceiveMemoryWarning:(void *)object | ||
65 | +{ | ||
66 | + [self clearMemory]; | ||
67 | +} | ||
68 | + | ||
69 | +- (void)willTerminate | ||
70 | +{ | ||
71 | + [self cleanDisk]; | ||
72 | +} | ||
73 | + | ||
74 | +#pragma mark ImageCache (private) | ||
75 | + | ||
76 | +- (NSString *)cachePathForKey:(NSString *)key | ||
77 | +{ | ||
78 | + const char *str = [key UTF8String]; | ||
79 | + unsigned char r[CC_MD5_DIGEST_LENGTH]; | ||
80 | + CC_MD5(str, strlen(str), r); | ||
81 | + NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", | ||
82 | + 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]]; | ||
83 | + | ||
84 | + return [diskCachePath stringByAppendingPathComponent:filename]; | ||
85 | +} | ||
86 | + | ||
87 | +#pragma mark ImageCache | ||
88 | + | ||
89 | ++ (DMImageCache *)sharedImageCache | ||
90 | +{ | ||
91 | + if (instance == nil) | ||
92 | + { | ||
93 | + instance = [[DMImageCache alloc] init]; | ||
94 | + } | ||
95 | + | ||
96 | + return instance; | ||
97 | +} | ||
98 | + | ||
99 | +- (void)storeImage:(UIImage *)image forKey:(NSString *)key | ||
100 | +{ | ||
101 | + [self storeImage:image forKey:key toDisk:YES]; | ||
102 | +} | ||
103 | + | ||
104 | +- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk | ||
105 | +{ | ||
106 | + if (image == nil) | ||
107 | + { | ||
108 | + return; | ||
109 | + } | ||
110 | + | ||
111 | + [cache setObject:image forKey:key]; | ||
112 | + | ||
113 | + if (toDisk) | ||
114 | + { | ||
115 | + [[NSFileManager defaultManager] createFileAtPath:[self cachePathForKey:key] contents:UIImageJPEGRepresentation(image, 1.0) attributes:nil]; | ||
116 | + } | ||
117 | +} | ||
118 | + | ||
119 | +- (UIImage *)imageFromKey:(NSString *)key | ||
120 | +{ | ||
121 | + return [self imageFromKey:key fromDisk:YES]; | ||
122 | +} | ||
123 | + | ||
124 | +- (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk | ||
125 | +{ | ||
126 | + UIImage *image = [cache objectForKey:key]; | ||
127 | + | ||
128 | + if (!image && fromDisk) | ||
129 | + { | ||
130 | + image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:[self cachePathForKey:key]]]; | ||
131 | + if (image != nil) | ||
132 | + { | ||
133 | + [cache setObject:image forKey:key]; | ||
134 | + [image release]; | ||
135 | + } | ||
136 | + } | ||
137 | + | ||
138 | + return image; | ||
139 | +} | ||
140 | + | ||
141 | +- (void)removeImageForKey:(NSString *)key | ||
142 | +{ | ||
143 | + [cache removeObjectForKey:key]; | ||
144 | + [[NSFileManager defaultManager] removeItemAtPath:[self cachePathForKey:key] error:nil]; | ||
145 | +} | ||
146 | + | ||
147 | +- (void)clearMemory | ||
148 | +{ | ||
149 | + [cache removeAllObjects]; | ||
150 | +} | ||
151 | + | ||
152 | +- (void)clearDisk | ||
153 | +{ | ||
154 | + [[NSFileManager defaultManager] removeItemAtPath:diskCachePath error:nil]; | ||
155 | + [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath attributes:nil]; | ||
156 | +} | ||
157 | + | ||
158 | +- (void)cleanDisk | ||
159 | +{ | ||
160 | + NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-kMaxCacheAge]; | ||
161 | + NSDirectoryEnumerator *fileEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:diskCachePath]; | ||
162 | + for (NSString *fileName in fileEnumerator) | ||
163 | + { | ||
164 | + NSString *filePath = [diskCachePath stringByAppendingPathComponent:fileName]; | ||
165 | + NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; | ||
166 | + if ([[[attrs fileModificationDate] laterDate:expirationDate] isEqualToDate:expirationDate]) | ||
167 | + { | ||
168 | + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; | ||
169 | + } | ||
170 | + } | ||
171 | +} | ||
172 | + | ||
173 | +@end |
DMWebImageView.h
0 → 100644
1 | +// | ||
2 | +// DMWebImageView.h | ||
3 | +// Dailymotion | ||
4 | +// | ||
5 | +// Created by Olivier Poitrey on 18/09/09. | ||
6 | +// Copyright 2009 Dailymotion. All rights reserved. | ||
7 | +// | ||
8 | + | ||
9 | +#import <UIKit/UIKit.h> | ||
10 | + | ||
11 | +@class DMWebImageDownloadOperation; | ||
12 | + | ||
13 | +@interface DMWebImageView : UIImageView | ||
14 | +{ | ||
15 | + UIImage *placeHolderImage; | ||
16 | + DMWebImageDownloadOperation *currentOperation; | ||
17 | +} | ||
18 | + | ||
19 | +- (void)setImageWithURL:(NSURL *)url; | ||
20 | +- (void)downloadFinishedWithImage:(UIImage *)image; | ||
21 | + | ||
22 | +@end | ||
23 | + | ||
24 | +@interface DMWebImageDownloadOperation : NSOperation | ||
25 | +{ | ||
26 | + NSURL *url; | ||
27 | + DMWebImageView *delegate; | ||
28 | +} | ||
29 | + | ||
30 | +@property (retain) NSURL *url; | ||
31 | +@property (assign) DMWebImageView *delegate; | ||
32 | + | ||
33 | +- (id)initWithURL:(NSURL *)url delegate:(DMWebImageView *)delegate; | ||
34 | + | ||
35 | +@end |
DMWebImageView.m
0 → 100644
1 | +// | ||
2 | +// DMWebImageView.m | ||
3 | +// Dailymotion | ||
4 | +// | ||
5 | +// Created by Olivier Poitrey on 18/09/09. | ||
6 | +// Copyright 2009 Dailymotion. All rights reserved. | ||
7 | +// | ||
8 | + | ||
9 | +#import "DMWebImageView.h" | ||
10 | +#import "DMImageCache.h" | ||
11 | + | ||
12 | +static NSOperationQueue *downloadQueue; | ||
13 | +static NSOperationQueue *cacheInQueue; | ||
14 | + | ||
15 | +@implementation DMWebImageView | ||
16 | + | ||
17 | +- (void)dealloc | ||
18 | +{ | ||
19 | + [placeHolderImage release]; | ||
20 | + [currentOperation release]; | ||
21 | + [super dealloc]; | ||
22 | +} | ||
23 | + | ||
24 | +#pragma mark RemoteImageView | ||
25 | + | ||
26 | +- (void)setImageWithURL:(NSURL *)url | ||
27 | +{ | ||
28 | + if (currentOperation != nil) | ||
29 | + { | ||
30 | + [currentOperation cancel]; // remove from queue | ||
31 | + [currentOperation release]; | ||
32 | + currentOperation = nil; | ||
33 | + } | ||
34 | + | ||
35 | + // Save the placeholder image in order to re-apply it when view is reused | ||
36 | + if (placeHolderImage == nil) | ||
37 | + { | ||
38 | + placeHolderImage = [self.image retain]; | ||
39 | + } | ||
40 | + else | ||
41 | + { | ||
42 | + self.image = placeHolderImage; | ||
43 | + } | ||
44 | + | ||
45 | + UIImage *cachedImage = [[DMImageCache sharedImageCache] imageFromKey:[url absoluteString]]; | ||
46 | + | ||
47 | + if (cachedImage) | ||
48 | + { | ||
49 | + self.image = cachedImage; | ||
50 | + } | ||
51 | + else | ||
52 | + { | ||
53 | + if (downloadQueue == nil) | ||
54 | + { | ||
55 | + downloadQueue = [[NSOperationQueue alloc] init]; | ||
56 | + [downloadQueue setMaxConcurrentOperationCount:8]; | ||
57 | + } | ||
58 | + | ||
59 | + currentOperation = [[DMWebImageDownloadOperation alloc] initWithURL:url delegate:self]; | ||
60 | + [downloadQueue addOperation:currentOperation]; | ||
61 | + } | ||
62 | +} | ||
63 | + | ||
64 | +- (void)downloadFinishedWithImage:(UIImage *)anImage | ||
65 | +{ | ||
66 | + self.image = anImage; | ||
67 | + [currentOperation release]; | ||
68 | + currentOperation = nil; | ||
69 | +} | ||
70 | + | ||
71 | +@end | ||
72 | + | ||
73 | +@implementation DMWebImageDownloadOperation | ||
74 | + | ||
75 | +@synthesize url, delegate; | ||
76 | + | ||
77 | +- (void)dealloc | ||
78 | +{ | ||
79 | + [url release]; | ||
80 | + [super dealloc]; | ||
81 | +} | ||
82 | + | ||
83 | + | ||
84 | +- (id)initWithURL:(NSURL *)anUrl delegate:(DMWebImageView *)aDelegate | ||
85 | +{ | ||
86 | + if (self = [super init]) | ||
87 | + { | ||
88 | + self.url = anUrl; | ||
89 | + self.delegate = aDelegate; | ||
90 | + } | ||
91 | + | ||
92 | + return self; | ||
93 | +} | ||
94 | + | ||
95 | +- (void)main | ||
96 | +{ | ||
97 | + if (self.isCancelled) | ||
98 | + { | ||
99 | + return; | ||
100 | + } | ||
101 | + | ||
102 | + NSData *data = [[NSData alloc] initWithContentsOfURL:url]; | ||
103 | + UIImage *image = [[UIImage alloc] initWithData:data]; | ||
104 | + [data release]; | ||
105 | + | ||
106 | + if (!self.isCancelled) | ||
107 | + { | ||
108 | + [delegate performSelectorOnMainThread:@selector(downloadFinishedWithImage:) withObject:image waitUntilDone:YES]; | ||
109 | + } | ||
110 | + | ||
111 | + if (cacheInQueue == nil) | ||
112 | + { | ||
113 | + cacheInQueue = [[NSOperationQueue alloc] init]; | ||
114 | + [cacheInQueue setMaxConcurrentOperationCount:2]; | ||
115 | + } | ||
116 | + | ||
117 | + NSString *cacheKey = [url absoluteString]; | ||
118 | + | ||
119 | + DMImageCache *imageCache = [DMImageCache sharedImageCache]; | ||
120 | + | ||
121 | + // Store image in memory cache NOW, no need to wait for the cache-in operation queue completion | ||
122 | + [imageCache storeImage:image forKey:cacheKey toDisk:NO]; | ||
123 | + | ||
124 | + // Perform the cache-in in another operation queue in order to not block a download operation slot | ||
125 | + NSInvocation *cacheInInvocation = [NSInvocation invocationWithMethodSignature:[[imageCache class] instanceMethodSignatureForSelector:@selector(storeImage:forKey:)]]; | ||
126 | + [cacheInInvocation setTarget:imageCache]; | ||
127 | + [cacheInInvocation setSelector:@selector(storeImage:forKey:)]; | ||
128 | + [cacheInInvocation setArgument:&image atIndex:2]; | ||
129 | + [cacheInInvocation setArgument:&cacheKey atIndex:3]; | ||
130 | + [cacheInInvocation retainArguments]; | ||
131 | + NSInvocationOperation *cacheInOperation = [[NSInvocationOperation alloc] initWithInvocation:cacheInInvocation]; | ||
132 | + [cacheInQueue addOperation:cacheInOperation]; | ||
133 | + [cacheInOperation release]; | ||
134 | + | ||
135 | + [image release]; | ||
136 | +} | ||
137 | + | ||
138 | +@end |
-
Please register or login to post a comment