Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions JMImageCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
- (UIImage *) imageForURL:(NSURL *)url key:(NSString*)key delegate:(id<JMImageCacheDelegate>)d;
- (UIImage *) imageForURL:(NSURL *)url delegate:(id<JMImageCacheDelegate>)d;

- (UIImage *) imageFromMemoryForURL:(NSURL *)url;
- (UIImage *) imageFromMemoryForKey:(NSString *)key;
- (UIImage *) imageFromDiskForKey:(NSString *)key;
- (UIImage *) imageFromDiskForURL:(NSURL *)url;

Expand Down
123 changes: 53 additions & 70 deletions JMImageCache.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
//

#import "JMImageCache.h"
#import <ImageIO/ImageIO.h>
#import "JMImageCacheOperation.h"

static inline NSString *JMImageCacheDirectory() {
static NSString *_JMImageCacheDirectory;
Expand All @@ -15,7 +17,7 @@
dispatch_once(&onceToken, ^{
_JMImageCacheDirectory = [[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Caches/JMCache"] copy];
});

return _JMImageCacheDirectory;
}
inline static NSString *keyForURL(NSURL *url) {
Expand All @@ -29,32 +31,37 @@
@interface JMImageCache ()

@property (strong, nonatomic) NSOperationQueue *diskOperationQueue;
@property (strong, nonatomic) NSOperationQueue *imageOperationQueue;

- (void) _downloadAndWriteImageForURL:(NSURL *)url key:(NSString *)key completionBlock:(void (^)(UIImage *image))completion failureBlock:(void (^)(NSURLRequest *request, NSURLResponse *response, NSError* error))failure;

@end

@implementation JMImageCache

@synthesize diskOperationQueue = _diskOperationQueue;
@synthesize diskOperationQueue;
@synthesize imageOperationQueue;


+ (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];

self.imageOperationQueue = [[NSOperationQueue alloc] init];
self.imageOperationQueue.maxConcurrentOperationCount = 8;

[[NSFileManager defaultManager] createDirectoryAtPath:JMImageCacheDirectory()
withIntermediateDirectories:YES
attributes:nil
Expand All @@ -65,70 +72,27 @@ - (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);
}

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

NSURLRequest* request = [NSURLRequest requestWithURL:url];
NSURLResponse* response = nil;
NSError* error = nil;
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

if (error)
{
dispatch_async(dispatch_get_main_queue(), ^{

if(failure) failure(request, response, error);
});
return;
}

UIImage *i = [[UIImage alloc] initWithData:data];
if (!i)
{
NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
[errorDetail setValue:[NSString stringWithFormat:@"Failed to init image with data from for URL: %@", url] forKey:NSLocalizedDescriptionKey];
NSError* error = [NSError errorWithDomain:@"JMImageCacheErrorDomain" code:1 userInfo:errorDetail];
dispatch_async(dispatch_get_main_queue(), ^{

if(failure) failure(request, response, error);
});
}
else
{
NSString *cachePath = cachePathForKey(key);
NSInvocation *writeInvocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(writeData:toPath:)]];

[writeInvocation setTarget:self];
[writeInvocation setSelector:@selector(writeData:toPath:)];
[writeInvocation setArgument:&data atIndex:2];
[writeInvocation setArgument:&cachePath atIndex:3];

[self performDiskWriteOperation:writeInvocation];
[self setImage:i forKey:key];

dispatch_async(dispatch_get_main_queue(), ^{
if(completion) completion(i);
});
}
});

JMImageCacheOperation *op = [[JMImageCacheOperation alloc] initWithURL:url aSuccessBlock:completion andFailedBlock:failure];
[self.imageOperationQueue addOperation:op];
}

- (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
Expand All @@ -141,13 +105,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
Expand All @@ -159,9 +123,9 @@ - (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 {
Expand All @@ -173,20 +137,30 @@ - (void) imageForURL:(NSURL *)url completionBlock:(void (^)(UIImage *image))comp
[self imageForURL:url key:keyForURL(url) completionBlock:completion failureBlock:(failure)];
}

- (UIImage *) cachedImageForKey:(NSString *)key {
- (UIImage *)imageFromMemoryForURL:(NSURL *)url {
return [super objectForKey:keyForURL(url)];
}

- (UIImage *) imageFromMemoryForKey:(NSString *)key {
if(!key) return nil;

return [super objectForKey:key];
}

- (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;
}

Expand All @@ -197,9 +171,9 @@ - (UIImage *) cachedImageForURL:(NSURL *)url {

- (UIImage *) imageForURL:(NSURL *)url key:(NSString*)key delegate:(id<JMImageCacheDelegate>)d {
if(!url) return nil;

UIImage *i = [self cachedImageForURL:url];

if(i) {
return i;
} else {
Expand All @@ -213,9 +187,9 @@ - (UIImage *) imageForURL:(NSURL *)url key:(NSString*)key delegate:(id<JMImageCa
}
}
}
failureBlock:nil];
failureBlock:nil];
}

return nil;
}

Expand All @@ -225,7 +199,7 @@ - (UIImage *) imageForURL:(NSURL *)url delegate:(id<JMImageCacheDelegate>)d {

- (UIImage *) imageFromDiskForKey:(NSString *)key {
UIImage *i = [[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:cachePathForKey(key) options:0 error:NULL]];
return i;
return i;
}

- (UIImage *) imageFromDiskForURL:(NSURL *)url {
Expand Down Expand Up @@ -255,6 +229,15 @@ - (void) removeImageForURL:(NSURL *)url {

- (void) writeData:(NSData*)data toPath:(NSString *)path {
[data writeToFile:path atomically:YES];
NSString *cacheName = @"memory-cache";

NSDictionary *properties = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];

NSNumber *actualCacheSize = [[NSUserDefaults standardUserDefaults] objectForKey:cacheName];
NSNumber *fileSize = [NSNumber numberWithUnsignedLong:([properties fileSize] + actualCacheSize.longValue)];

[[NSUserDefaults standardUserDefaults] setObject:fileSize forKey:cacheName];

}
- (void) performDiskWriteOperation:(NSInvocation *)invoction {
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithInvocation:invoction];
Expand Down
20 changes: 20 additions & 0 deletions JMImageCacheOperation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// JMImageCacheOperation.h
// JMCache
//
// Created by Rodrigo Garcia on 7/10/13.
// Copyright 2011 Jake Marsh. All rights reserved.
//

#import <Foundation/Foundation.h>
typedef void (^JMImageCacheSuccessBlock) (UIImage *image);
typedef void (^JMImageCacheFailedBlock) (NSURLRequest *request, NSURLResponse *response, NSError* error);

@interface JMImageCacheOperation : NSOperation

@property(copy,nonatomic) JMImageCacheSuccessBlock successBlock;
@property(copy,nonatomic) JMImageCacheFailedBlock failedBlock;

-(id)initWithURL:(NSURL *)aURL aSuccessBlock:(JMImageCacheSuccessBlock)aSuccessBlock andFailedBlock:(JMImageCacheFailedBlock)aFailedBlock;

@end
116 changes: 116 additions & 0 deletions JMImageCacheOperation.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//
// JMImageCacheOperation.m
// JMCache
//
// Created by Rodrigo Garcia on 7/10/13.
//
//

#import "JMImageCacheOperation.h"

static inline NSString *JMImageCacheDirectory() {
static NSString *_JMImageCacheDirectory;
static dispatch_once_t onceToken;

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]];
return [JMImageCacheDirectory() stringByAppendingPathComponent:fileName];
}


@interface JMImageCacheOperation ()

@property(copy,nonatomic) NSURL *url;
@property(strong,nonatomic) UIImage *imageOperation;
@property(strong,nonatomic) NSData *dataRepresentation;

@end

@implementation JMImageCacheOperation

-(id)initWithURL:(NSURL *)aURL aSuccessBlock:(JMImageCacheSuccessBlock)aSuccessBlock andFailedBlock:(JMImageCacheFailedBlock)aFailedBlock
{
if(self=[super init])
{
self.url = aURL;
self.successBlock = aSuccessBlock;
self.failedBlock = aFailedBlock;
}

return self;
}

-(void)main
{
[self downloadImage];
NSString *cachePath = cachePathForKey(self.url.absoluteString);
[self writeData:self.dataRepresentation toPath:cachePath];

if (!self.isCancelled && self.imageOperation)
{
dispatch_async(dispatch_get_main_queue(), ^{
if(self.successBlock) self.successBlock(self.imageOperation);
});
}
}


- (void)downloadImage
{
NSURLRequest* request = [NSURLRequest requestWithURL:self.url];
NSURLResponse* response = nil;
NSError* error = nil;
self.dataRepresentation = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

if (error)
{
dispatch_async(dispatch_get_main_queue(), ^{

if(self.failedBlock) self.failedBlock(request, response, error);
});
return;
}

self.imageOperation = [[UIImage alloc] initWithData:self.dataRepresentation];

if (!self.imageOperation)
{
NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
[errorDetail setValue:[NSString stringWithFormat:@"Failed to init image with data from for URL: %@", self.url] forKey:NSLocalizedDescriptionKey];
NSError* error = [NSError errorWithDomain:@"JMImageCacheErrorDomain" code:1 userInfo:errorDetail];
dispatch_async(dispatch_get_main_queue(), ^{

if(self.failedBlock) self.failedBlock(request, response, error);
});
}
}


- (void)writeData:(NSData*)data toPath:(NSString *)path {
[data writeToFile:path atomically:YES];
NSString *cacheName = @"memory-cache";

NSDictionary *properties = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];

NSNumber *actualCacheSize = [[NSUserDefaults standardUserDefaults] objectForKey:cacheName];
NSNumber *fileSize = [NSNumber numberWithUnsignedLong:([properties fileSize] + actualCacheSize.longValue)];

[[NSUserDefaults standardUserDefaults] setObject:fileSize forKey:cacheName];

}

-(BOOL)isConcurrent
{
return YES;
}

@end
Loading