diff --git a/Demo/JMImageCacheDemo.xcodeproj/project.pbxproj b/Demo/JMImageCacheDemo.xcodeproj/project.pbxproj index 593613c..aba587d 100755 --- a/Demo/JMImageCacheDemo.xcodeproj/project.pbxproj +++ b/Demo/JMImageCacheDemo.xcodeproj/project.pbxproj @@ -168,7 +168,7 @@ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0440; + LastUpgradeCheck = 0510; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "JMImageCacheDemo" */; compatibilityVersion = "Xcode 3.2"; @@ -254,11 +254,11 @@ C01FCF4F08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; GCC_C_LANGUAGE_STANDARD = c99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; name = Debug; @@ -266,7 +266,6 @@ C01FCF5008A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; GCC_C_LANGUAGE_STANDARD = c99; GCC_WARN_ABOUT_RETURN_TYPE = YES; diff --git a/JMImageCache.m b/JMImageCache.m index 007958e..a35ea0e 100644 --- a/JMImageCache.m +++ b/JMImageCache.m @@ -15,14 +15,14 @@ dispatch_once(&onceToken, ^{ _JMImageCacheDirectory = [[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Caches/JMCache"] copy]; }); - + return _JMImageCacheDirectory; } inline static NSString *keyForURL(NSURL *url) { return [url absoluteString]; } static inline NSString *cachePathForKey(NSString *key) { - NSString *fileName = [NSString stringWithFormat:@"JMImageCache-%u", [key hash]]; + NSString *fileName = [NSString stringWithFormat:@"JMImageCache-%lu", (unsigned long)[key hash]]; return [JMImageCacheDirectory() stringByAppendingPathComponent:fileName]; } @@ -41,20 +41,20 @@ @implementation JMImageCache + (JMImageCache *) sharedCache { static JMImageCache *_sharedCache = nil; static dispatch_once_t onceToken; - + dispatch_once(&onceToken, ^{ _sharedCache = [[JMImageCache alloc] init]; }); - + return _sharedCache; } - (id) init { self = [super init]; if(!self) return nil; - + self.diskOperationQueue = [[NSOperationQueue alloc] init]; - + [[NSFileManager defaultManager] createDirectoryAtPath:JMImageCacheDirectory() withIntermediateDirectories:YES attributes:nil @@ -65,14 +65,24 @@ - (id) init { - (void) _downloadAndWriteImageForURL:(NSURL *)url key:(NSString *)key completionBlock:(void (^)(UIImage *image))completion failureBlock:(void (^)(NSURLRequest *request, NSURLResponse *response, NSError* error))failure { if (!key && !url) return; - + if (!key) { key = keyForURL(url); } - + + __weak JMImageCache *safeSelf = self; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSURLRequest* request = [NSURLRequest requestWithURL:url]; + NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url]; + + NSDate *lastUpdated = [safeSelf dateForImageKey:key]; + NSString *HTTPdate = [safeSelf httpDateForDate:lastUpdated]; + if (HTTPdate) { + [request addValue:HTTPdate forHTTPHeaderField:@"If-Modified-Since"]; + } + NSURLResponse* response = nil; NSError* error = nil; NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; @@ -86,8 +96,15 @@ - (void) _downloadAndWriteImageForURL:(NSURL *)url key:(NSString *)key completio return; } - UIImage *i = [[UIImage alloc] initWithData:data]; - if (!i) + NSInteger kUnModifiedHttpCode = 304; + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if (httpResponse.statusCode == kUnModifiedHttpCode) + { + return; + } + + UIImage *imageDownloaded = [[UIImage alloc] initWithData:data]; + if (!imageDownloaded) { NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary]; [errorDetail setValue:[NSString stringWithFormat:@"Failed to init image with data from for URL: %@", url] forKey:NSLocalizedDescriptionKey]; @@ -102,33 +119,42 @@ - (void) _downloadAndWriteImageForURL:(NSURL *)url key:(NSString *)key completio NSString *cachePath = cachePathForKey(key); NSInvocation *writeInvocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(writeData:toPath:)]]; - [writeInvocation setTarget:self]; + [writeInvocation setTarget:safeSelf]; [writeInvocation setSelector:@selector(writeData:toPath:)]; [writeInvocation setArgument:&data atIndex:2]; [writeInvocation setArgument:&cachePath atIndex:3]; - [self performDiskWriteOperation:writeInvocation]; - [self setImage:i forKey:key]; + [safeSelf performDiskWriteOperation:writeInvocation]; + [safeSelf setImage:imageDownloaded forKey:key]; dispatch_async(dispatch_get_main_queue(), ^{ - if(completion) completion(i); + if(completion) completion(imageDownloaded); }); } }); } +- (NSString *)httpDateForDate:(NSDate *)lastUpdated { + NSDateFormatter* httpDateFormatter = [[NSDateFormatter alloc] init]; + httpDateFormatter.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'"; + httpDateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; + httpDateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; + NSString *HTTPdate = [httpDateFormatter stringFromDate:lastUpdated]; + return HTTPdate; +} + - (void) removeAllObjects { [super removeAllObjects]; - + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSFileManager *fileMgr = [NSFileManager defaultManager]; NSError *error = nil; NSArray *directoryContents = [fileMgr contentsOfDirectoryAtPath:JMImageCacheDirectory() error:&error]; - + if (error == nil) { for (NSString *path in directoryContents) { NSString *fullPath = [JMImageCacheDirectory() stringByAppendingPathComponent:path]; - + BOOL removeSuccess = [fileMgr removeItemAtPath:fullPath error:&error]; if (!removeSuccess) { //Error Occured @@ -141,13 +167,13 @@ - (void) removeAllObjects { } - (void) removeObjectForKey:(id)key { [super removeObjectForKey:key]; - + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSFileManager *fileMgr = [NSFileManager defaultManager]; NSString *cachePath = cachePathForKey(key); - + NSError *error = nil; - + BOOL removeSuccess = [fileMgr removeItemAtPath:cachePath error:&error]; if (!removeSuccess) { //Error Occured @@ -159,14 +185,18 @@ - (void) removeObjectForKey:(id)key { #pragma mark Getter Methods - (void) imageForURL:(NSURL *)url key:(NSString *)key completionBlock:(void (^)(UIImage *image))completion failureBlock:(void (^)(NSURLRequest *request, NSURLResponse *response, NSError* error))failure{ - - UIImage *i = [self cachedImageForKey:key]; - - if(i) { - if(completion) completion(i); - } else { - [self _downloadAndWriteImageForURL:url key:key completionBlock:completion failureBlock:failure]; + + if (!key) { + key = keyForURL(url); } + + UIImage *cachedImage = [self cachedImageForKey:key]; + + if(cachedImage) { + if(completion) completion(cachedImage); + } + [self _downloadAndWriteImageForURL:url key:key completionBlock:completion failureBlock:failure]; + } - (void) imageForURL:(NSURL *)url completionBlock:(void (^)(UIImage *image))completion failureBlock:(void (^)(NSURLRequest *request, NSURLResponse *response, NSError* error))failure{ @@ -175,18 +205,18 @@ - (void) imageForURL:(NSURL *)url completionBlock:(void (^)(UIImage *image))comp - (UIImage *) cachedImageForKey:(NSString *)key { if(!key) return nil; - + id returner = [super objectForKey:key]; - + if(returner) { return returner; } else { UIImage *i = [self imageFromDiskForKey:key]; if(i) [self setImage:i forKey:key]; - + return i; } - + return nil; } @@ -197,9 +227,9 @@ - (UIImage *) cachedImageForURL:(NSURL *)url { - (UIImage *) imageForURL:(NSURL *)url key:(NSString*)key delegate:(id)d { if(!url) return nil; - + UIImage *i = [self cachedImageForURL:url]; - + if(i) { return i; } else { @@ -213,9 +243,9 @@ - (UIImage *) imageForURL:(NSURL *)url key:(NSString*)key delegate:(id)d { } - (UIImage *) imageFromDiskForKey:(NSString *)key { - UIImage *i = [[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:cachePathForKey(key) options:0 error:NULL]]; + + NSString *pathToImage = cachePathForKey(key); + + UIImage *i = [[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:pathToImage options:0 error:NULL]]; return i; } +- (NSDate *)dateForImageKey:(NSString *)key { + + NSString *pathToImage = cachePathForKey(key); + + if(!pathToImage) + return nil; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSDictionary* imageAttribute = [fileManager attributesOfItemAtPath:pathToImage error:nil]; + NSDate *lastUpdated = [imageAttribute fileModificationDate]; + + return lastUpdated; +} + - (UIImage *) imageFromDiskForURL:(NSURL *)url { return [self imageFromDiskForKey:keyForURL(url)]; } diff --git a/JMImageCache.podspec b/JMImageCache.podspec index b061fa2..984467c 100644 --- a/JMImageCache.podspec +++ b/JMImageCache.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "JMImageCache" - s.version = "0.4.0" + s.version = "0.4.2" s.summary = "NSCache based remote-image caching and downloading mechanism for iOS." s.description = 'NSCache based remote-image caching and downloading mechanism for iOS. Is block based and uses a simple UIImageView category to handle loading images with placeholders. With fix to the placeholder behaviour' @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.authors = { "Jake Marsh" => "jake@deallocatedobjects.com" } - s.source = { :git => "https://github.com/jakemarsh/JMImageCache.git", :tag => "0.4.0" } + s.source = { :git => "https://github.com/antoinerabanes/JMImageCache.git", :tag => s.version.to_s } s.platform = :ios, '5.0' s.requires_arc = true diff --git a/UIImageView+JMImageCache.m b/UIImageView+JMImageCache.m index 3a3502a..821e561 100644 --- a/UIImageView+JMImageCache.m +++ b/UIImageView+JMImageCache.m @@ -53,67 +53,61 @@ - (void) setImageWithURL:(NSURL *)url key:(NSString*)key placeholder:(UIImage *) [self setImageWithURL:url key:key placeholder:placeholderImage completionBlock:nil]; } - (void) setImageWithURL:(NSURL *)url key:(NSString*)key placeholder:(UIImage *)placeholderImage completionBlock:(void (^)(UIImage *image))completionBlock { - [self setImageWithURL:url key:key placeholder:placeholderImage completionBlock:completionBlock]; + [self setImageWithURL:url key:key placeholder:placeholderImage completionBlock:completionBlock failureBlock:nil]; } + - (void) setImageWithURL:(NSURL *)url key:(NSString*)key placeholder:(UIImage *)placeholderImage completionBlock:(void (^)(UIImage *image))completionBlock failureBlock:(void (^)(NSURLRequest *request, NSURLResponse *response, NSError* error))failureBlock{ self.jm_imageURL = url; - self.image = placeholderImage; - - [self setNeedsDisplay]; - [self setNeedsLayout]; - + [self assignImage:placeholderImage]; + __weak UIImageView *safeSelf = self; - + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - UIImage *i; - - if (key) { - i = [[JMImageCache sharedCache] cachedImageForKey:key]; - } else { - i = [[JMImageCache sharedCache] cachedImageForURL:url]; - } - - if(i) { - dispatch_async(dispatch_get_main_queue(), ^{ - safeSelf.jm_imageURL = nil; - - safeSelf.image = i; - - [safeSelf setNeedsLayout]; - [safeSelf setNeedsDisplay]; - }); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - safeSelf.image = placeholderImage; - - [safeSelf setNeedsDisplay]; - [safeSelf setNeedsLayout]; - }); - - [[JMImageCache sharedCache] imageForURL:url key:key completionBlock:^(UIImage *image) { - if ([url isEqual:safeSelf.jm_imageURL]) { - dispatch_async(dispatch_get_main_queue(), ^{ - if(image) { - safeSelf.image = image; - } else { - safeSelf.image = placeholderImage; - } - - safeSelf.jm_imageURL = nil; - - [safeSelf setNeedsLayout]; - [safeSelf setNeedsDisplay]; - - if (completionBlock) completionBlock(image); - }); + [[JMImageCache sharedCache] imageForURL:url key:key completionBlock:^(UIImage *image) { + if ([url isEqual:safeSelf.jm_imageURL]) { + + if (image) { + [safeSelf assignImageOnMainQueue:image]; + } else { + [safeSelf assignImageOnMainQueue:placeholderImage]; } + dispatch_async(dispatch_get_main_queue(), ^{ + + safeSelf.jm_imageURL = nil; + + if (completionBlock) completionBlock(image); + }); } - failureBlock:^(NSURLRequest *request, NSURLResponse *response, NSError* error) - { - if (failureBlock) failureBlock(request, response, error); - }]; } + failureBlock:^(NSURLRequest *request, NSURLResponse *response, NSError* error) + { + if (failureBlock) failureBlock(request, response, error); + }]; + + }); +} + +- (void)assignImageOnMainQueue:(UIImage *)cachedImage { + dispatch_async(dispatch_get_main_queue(), ^{ + [self assignImage:cachedImage]; }); } +- (UIImage *)getImageFromSharedCacheWith:(NSURL *)url key:(NSString *)key { + UIImage *cachedImage; + if (key) { + cachedImage = [[JMImageCache sharedCache] cachedImageForKey:key]; + } else { + cachedImage = [[JMImageCache sharedCache] cachedImageForURL:url]; + } + return cachedImage; +} + +- (void)assignImage:(UIImage *)cachedImage { + self.image = cachedImage; + + [self setNeedsLayout]; + [self setNeedsDisplay]; +} + @end \ No newline at end of file