Skip to content

Commit

Permalink
Merge branch 'develop' into ogimageview
Browse files Browse the repository at this point in the history
Conflicts:
	OGImageDemo/OGImageDemo/OGImageTableViewCell.m
	OGImageDemo/OGImageDemo/OGViewController.m
  • Loading branch information
artgillespie committed Aug 27, 2013
2 parents 193aba9 + c8f3571 commit 47b94c4
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 26 deletions.
12 changes: 11 additions & 1 deletion OGImage/OGImageCache.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ typedef void (^OGImageCacheCompletionBlock)(__OGImage *image);


+ (NSString *)filePathForKey:(NSString *)key; + (NSString *)filePathForKey:(NSString *)key;


+ (NSURL *)fileURLForKey:(NSString *)key;

/** /**
* Check in-memory and on-disk caches for image corresponding to `key`. `block` * Check in-memory and on-disk caches for image corresponding to `key`. `block`
* called on main queue when check is complete. If `image` parameter is `nil`, * called on main queue when check is complete. If `image` parameter is `nil`,
Expand All @@ -30,7 +32,9 @@ typedef void (^OGImageCacheCompletionBlock)(__OGImage *image);


/** /**
* Remove all cached images from in-memory and on-disk caches. If `wait` is `YES` * Remove all cached images from in-memory and on-disk caches. If `wait` is `YES`
* this will block the calling thread until the purge is complete. * this will block the calling thread until the purge is complete. In either case,
* this method manages its own `UIBackgroundTaskIdentifier` — it's safe to call it
* from `applicationDidEnterBackground`
*/ */
- (void)purgeCache:(BOOL)wait; - (void)purgeCache:(BOOL)wait;


Expand All @@ -46,5 +50,11 @@ typedef void (^OGImageCacheCompletionBlock)(__OGImage *image);
*/ */
- (void)purgeMemoryCacheForKey:(NSString *)key andWait:(BOOL)wait; - (void)purgeMemoryCacheForKey:(NSString *)key andWait:(BOOL)wait;


/**
* Remove cached images from disk that haven't been accessed since `date`
* This method manages its own `UIBackgroundTaskIdentifier` — it's safe to call it
* from `applicationDidEnterBackground`
*/
- (void)purgeDiskCacheOfImagesLastAccessedBefore:(NSDate *)date;


@end @end
73 changes: 57 additions & 16 deletions OGImage/OGImageCache.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@


static OGImageCache *OGImageCacheShared; static OGImageCache *OGImageCacheShared;


NSString *OGImageCachePath() { NSURL *OGImageCacheURL() {
// generate the cache path: <app>/Library/Application Support/<bundle identifier>/OGImageCache, // generate the cache path: <app>/Library/Application Support/<bundle identifier>/OGImageCache,
// creating the directories as needed // creating the directories as needed
NSArray *array = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); NSArray *array = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask];
if (nil == array || 0 == [array count]) { if (nil == array || 0 == [array count]) {
return nil; return nil;
} }
NSString *cachePath = [[array[0] stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] stringByAppendingPathComponent:@"OGImageCache"]; NSURL *cacheURL = [[array[0] URLByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] URLByAppendingPathComponent:@"OGImageCache"];
[[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:nil]; [[NSFileManager defaultManager] createDirectoryAtURL:cacheURL withIntermediateDirectories:YES attributes:nil error:nil];
return cachePath; return cacheURL;
} }


@implementation OGImageCache { @implementation OGImageCache {
NSCache *_memoryCache; NSCache *_memoryCache;
dispatch_queue_t _cacheFileReadQueue;
dispatch_queue_t _cacheFileTasksQueue; dispatch_queue_t _cacheFileTasksQueue;
} }


Expand All @@ -49,16 +50,30 @@ + (NSString *)MD5:(NSString *)string {
} }


+ (NSString *)filePathForKey:(NSString *)key { + (NSString *)filePathForKey:(NSString *)key {
return [OGImageCachePath() stringByAppendingPathComponent:[OGImageCache MD5:key]]; return [[OGImageCache fileURLForKey:key] path];
}

+ (NSURL *)fileURLForKey:(NSString *)key {
return [OGImageCacheURL() URLByAppendingPathComponent:[OGImageCache MD5:key]];
} }


- (id)init { - (id)init {
self = [super init]; self = [super init];
if (self) { if (self) {
_memoryCache = [[NSCache alloc] init]; _memoryCache = [[NSCache alloc] init];
[_memoryCache setName:@"com.origamilabs.OGImageCache"]; [_memoryCache setName:@"com.origamilabs.OGImageCache"];
/*
* We use the 'queue-jumping' pattern outlined in WWDC 2011 Session 201: "Mastering Grand Central Dispatch"
* We place lower-priority tasks (writing, purging) on a serial queue that has its
* target queue set to our high-priority (read) queue. Whenever we submit a high-priority
* block, we suspend the lower-priority queue for the duration of the block.
*
* This way, writes and purges never cause cache reads to wait in the queue.
*/
_cacheFileReadQueue = dispatch_queue_create("com.origamilabs.OGImageCache.read", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(_cacheFileReadQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
_cacheFileTasksQueue = dispatch_queue_create("com.origamilabs.OGImageCache.filetasks", DISPATCH_QUEUE_SERIAL); _cacheFileTasksQueue = dispatch_queue_create("com.origamilabs.OGImageCache.filetasks", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(_cacheFileTasksQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); dispatch_set_target_queue(_cacheFileTasksQueue, _cacheFileReadQueue);
} }
return self; return self;
} }
Expand All @@ -71,10 +86,11 @@ - (void)imageForKey:(NSString *)key block:(OGImageCacheCompletionBlock)block {
block(image); block(image);
return; return;
} }
dispatch_async(_cacheFileTasksQueue, ^{ dispatch_suspend(_cacheFileTasksQueue);
dispatch_async(_cacheFileReadQueue, ^{
// Check to see if the image is cached locally // Check to see if the image is cached locally
NSString *cachePath = [OGImageCache filePathForKey:(key)]; NSURL *cacheURL = [OGImageCache fileURLForKey:(key)];
__OGImage *image = [[__OGImage alloc] initWithDataAtURL:[NSURL fileURLWithPath:cachePath]]; __OGImage *image = [[__OGImage alloc] initWithDataAtURL:cacheURL];
// if we have the image in the on-disk cache, store it to the in-memory cache // if we have the image in the on-disk cache, store it to the in-memory cache
if (nil != image) { if (nil != image) {
[_memoryCache setObject:image forKey:key]; [_memoryCache setObject:image forKey:key];
Expand All @@ -83,6 +99,7 @@ - (void)imageForKey:(NSString *)key block:(OGImageCacheCompletionBlock)block {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
block(image); block(image);
}); });
dispatch_resume(_cacheFileTasksQueue);
}); });
} }


Expand All @@ -91,7 +108,7 @@ - (void)setImage:(__OGImage *)image forKey:(NSString *)key {
NSParameterAssert(nil != key); NSParameterAssert(nil != key);
[_memoryCache setObject:image forKey:key]; [_memoryCache setObject:image forKey:key];
dispatch_async(_cacheFileTasksQueue, ^{ dispatch_async(_cacheFileTasksQueue, ^{
NSURL *fileURL = [NSURL fileURLWithPath:[OGImageCache filePathForKey:key]]; NSURL *fileURL = [OGImageCache fileURLForKey:key];
NSError *error; NSError *error;
if(![image writeToURL:fileURL error:&error]) { if(![image writeToURL:fileURL error:&error]) {
NSLog(@"[OGImageCache ERROR] failed to write image with error %@ %s %d", error, __FILE__, __LINE__); NSLog(@"[OGImageCache ERROR] failed to write image with error %@ %s %d", error, __FILE__, __LINE__);
Expand All @@ -105,11 +122,15 @@ - (void)setImage:(__OGImage *)image forKey:(NSString *)key {


- (void)purgeCache:(BOOL)wait { - (void)purgeCache:(BOOL)wait {
[_memoryCache removeAllObjects]; [_memoryCache removeAllObjects];
UIBackgroundTaskIdentifier taskId = UIBackgroundTaskInvalid;
taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:taskId];
}];
void (^purgeFilesBlock)(void) = ^{ void (^purgeFilesBlock)(void) = ^{
NSString *cachePath = OGImageCachePath(); for (NSURL *url in [[NSFileManager defaultManager] enumeratorAtURL:OGImageCacheURL() includingPropertiesForKeys:nil options:0 errorHandler:nil]) {
for (NSString *file in [[NSFileManager defaultManager] enumeratorAtPath:cachePath]) { [[NSFileManager defaultManager] removeItemAtURL:url error:nil];
[[NSFileManager defaultManager] removeItemAtPath:[cachePath stringByAppendingPathComponent:file] error:nil];
} }
[[UIApplication sharedApplication] endBackgroundTask:taskId];
}; };
if (YES == wait) { if (YES == wait) {
dispatch_sync(_cacheFileTasksQueue, purgeFilesBlock); dispatch_sync(_cacheFileTasksQueue, purgeFilesBlock);
Expand All @@ -123,10 +144,10 @@ - (void)purgeCacheForKey:(NSString *)key andWait:(BOOL)wait {


[self purgeMemoryCacheForKey:key andWait:wait]; [self purgeMemoryCacheForKey:key andWait:wait];


NSString *cachedFilePath = [[self class] filePathForKey:key]; NSURL *cachedFileURL = [[self class] fileURLForKey:key];


void (^purgeFileBlock)(void) =^{ void (^purgeFileBlock)(void) =^{
[[NSFileManager defaultManager] removeItemAtPath:cachedFilePath error:nil]; [[NSFileManager defaultManager] removeItemAtURL:cachedFileURL error:nil];
}; };


if (YES == wait) { if (YES == wait) {
Expand All @@ -142,4 +163,24 @@ - (void)purgeMemoryCacheForKey:(NSString *)key andWait:(BOOL)wait {
[_memoryCache removeObjectForKey:key]; [_memoryCache removeObjectForKey:key];
} }


- (void)purgeDiskCacheOfImagesLastAccessedBefore:(NSDate *)date {
UIBackgroundTaskIdentifier taskId = UIBackgroundTaskInvalid;
taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:taskId];
}];
dispatch_async(_cacheFileTasksQueue, ^{
NSURL *cacheURL = OGImageCacheURL();
for (NSURL *fileURL in [[NSFileManager defaultManager] enumeratorAtURL:cacheURL includingPropertiesForKeys:@[NSURLContentAccessDateKey] options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:nil]) {
NSDate *accessDate;
if (NO == [fileURL getResourceValue:&accessDate forKey:NSURLContentAccessDateKey error:nil]) {
return;
}
if (NSOrderedDescending == [date compare:accessDate]) {
[[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
}
}
[[UIApplication sharedApplication] endBackgroundTask:taskId];
});
}

@end @end
3 changes: 1 addition & 2 deletions OGImage/__OGImage.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ - (id)initWithData:(NSData *)data scale:(CGFloat)scale {
// do we have an OGImageDictionary? // do we have an OGImageDictionary?
_originalFileType = (__bridge NSString *)CGImageSourceGetType(imageSource); _originalFileType = (__bridge NSString *)CGImageSourceGetType(imageSource);
_originalFileAlphaInfo = CGImageGetAlphaInfo(cgImage); _originalFileAlphaInfo = CGImageGetAlphaInfo(cgImage);
NSDictionary *propDict = CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL)); _originalFileOrientation = [_originalFileProperties[(__bridge NSString *)kCGImagePropertyOrientation] integerValue];
_originalFileOrientation = [propDict[(__bridge NSString *)kCGImagePropertyOrientation] integerValue];
self = [super initWithCGImage:cgImage scale:scale orientation:OGEXIFOrientationToUIImageOrientation(_originalFileOrientation)]; self = [super initWithCGImage:cgImage scale:scale orientation:OGEXIFOrientationToUIImageOrientation(_originalFileOrientation)];
CGImageRelease(cgImage); CGImageRelease(cgImage);
} }
Expand Down
12 changes: 5 additions & 7 deletions OGImageDemo/OGImageDemo/OGAppDelegate.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import "OGViewController.h" #import "OGViewController.h"
#import "DDLog.h" #import "DDLog.h"
#import "DDTTYLogger.h" #import "DDTTYLogger.h"
#import "OGImageCache.h"


@implementation OGAppDelegate @implementation OGAppDelegate


Expand All @@ -29,25 +30,22 @@ - (void)setupLogging {
} }


- (void)applicationWillResignActive:(UIApplication *)application { - (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
} }


- (void)applicationDidEnterBackground:(UIApplication *)application { - (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // purge the disk cache of any image that hasn't been
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. // accessed more recently than 2 minutes ago. This is obviously pretty contrived;
NSDate *before = [NSDate dateWithTimeIntervalSinceNow:-120.];
[[OGImageCache shared] purgeDiskCacheOfImagesLastAccessedBefore:before];
} }


- (void)applicationWillEnterForeground:(UIApplication *)application { - (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
} }


- (void)applicationDidBecomeActive:(UIApplication *)application { - (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
} }


- (void)applicationWillTerminate:(UIApplication *)application { - (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
} }


@end @end

0 comments on commit 47b94c4

Please sign in to comment.