Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'develop' into ogimageview

Conflicts:
	OGImageDemo/OGImageDemo/OGImageTableViewCell.m
	OGImageDemo/OGImageDemo/OGViewController.m
  • Loading branch information...
commit 47b94c481b8613a833b49c1a4f5692bbc633fe3a 2 parents 193aba9 + c8f3571
@artgillespie authored
View
12 OGImage/OGImageCache.h
@@ -19,6 +19,8 @@ typedef void (^OGImageCacheCompletionBlock)(__OGImage *image);
+ (NSString *)filePathForKey:(NSString *)key;
++ (NSURL *)fileURLForKey:(NSString *)key;
+
/**
* 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`,
@@ -30,7 +32,9 @@ typedef void (^OGImageCacheCompletionBlock)(__OGImage *image);
/**
* 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;
@@ -46,5 +50,11 @@ typedef void (^OGImageCacheCompletionBlock)(__OGImage *image);
*/
- (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
View
73 OGImage/OGImageCache.m
@@ -12,20 +12,21 @@
static OGImageCache *OGImageCacheShared;
-NSString *OGImageCachePath() {
+NSURL *OGImageCacheURL() {
// generate the cache path: <app>/Library/Application Support/<bundle identifier>/OGImageCache,
// 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]) {
return nil;
}
- NSString *cachePath = [[array[0] stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] stringByAppendingPathComponent:@"OGImageCache"];
- [[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:nil];
- return cachePath;
+ NSURL *cacheURL = [[array[0] URLByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] URLByAppendingPathComponent:@"OGImageCache"];
+ [[NSFileManager defaultManager] createDirectoryAtURL:cacheURL withIntermediateDirectories:YES attributes:nil error:nil];
+ return cacheURL;
}
@implementation OGImageCache {
NSCache *_memoryCache;
+ dispatch_queue_t _cacheFileReadQueue;
dispatch_queue_t _cacheFileTasksQueue;
}
@@ -49,7 +50,11 @@ + (NSString *)MD5:(NSString *)string {
}
+ (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 {
@@ -57,8 +62,18 @@ - (id)init {
if (self) {
_memoryCache = [[NSCache alloc] init];
[_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);
- dispatch_set_target_queue(_cacheFileTasksQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
+ dispatch_set_target_queue(_cacheFileTasksQueue, _cacheFileReadQueue);
}
return self;
}
@@ -71,10 +86,11 @@ - (void)imageForKey:(NSString *)key block:(OGImageCacheCompletionBlock)block {
block(image);
return;
}
- dispatch_async(_cacheFileTasksQueue, ^{
+ dispatch_suspend(_cacheFileTasksQueue);
+ dispatch_async(_cacheFileReadQueue, ^{
// Check to see if the image is cached locally
- NSString *cachePath = [OGImageCache filePathForKey:(key)];
- __OGImage *image = [[__OGImage alloc] initWithDataAtURL:[NSURL fileURLWithPath:cachePath]];
+ NSURL *cacheURL = [OGImageCache fileURLForKey:(key)];
+ __OGImage *image = [[__OGImage alloc] initWithDataAtURL:cacheURL];
// if we have the image in the on-disk cache, store it to the in-memory cache
if (nil != image) {
[_memoryCache setObject:image forKey:key];
@@ -83,6 +99,7 @@ - (void)imageForKey:(NSString *)key block:(OGImageCacheCompletionBlock)block {
dispatch_async(dispatch_get_main_queue(), ^{
block(image);
});
+ dispatch_resume(_cacheFileTasksQueue);
});
}
@@ -91,7 +108,7 @@ - (void)setImage:(__OGImage *)image forKey:(NSString *)key {
NSParameterAssert(nil != key);
[_memoryCache setObject:image forKey:key];
dispatch_async(_cacheFileTasksQueue, ^{
- NSURL *fileURL = [NSURL fileURLWithPath:[OGImageCache filePathForKey:key]];
+ NSURL *fileURL = [OGImageCache fileURLForKey:key];
NSError *error;
if(![image writeToURL:fileURL error:&error]) {
NSLog(@"[OGImageCache ERROR] failed to write image with error %@ %s %d", error, __FILE__, __LINE__);
@@ -105,11 +122,15 @@ - (void)setImage:(__OGImage *)image forKey:(NSString *)key {
- (void)purgeCache:(BOOL)wait {
[_memoryCache removeAllObjects];
+ UIBackgroundTaskIdentifier taskId = UIBackgroundTaskInvalid;
+ taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
+ [[UIApplication sharedApplication] endBackgroundTask:taskId];
+ }];
void (^purgeFilesBlock)(void) = ^{
- NSString *cachePath = OGImageCachePath();
- for (NSString *file in [[NSFileManager defaultManager] enumeratorAtPath:cachePath]) {
- [[NSFileManager defaultManager] removeItemAtPath:[cachePath stringByAppendingPathComponent:file] error:nil];
+ for (NSURL *url in [[NSFileManager defaultManager] enumeratorAtURL:OGImageCacheURL() includingPropertiesForKeys:nil options:0 errorHandler:nil]) {
+ [[NSFileManager defaultManager] removeItemAtURL:url error:nil];
}
+ [[UIApplication sharedApplication] endBackgroundTask:taskId];
};
if (YES == wait) {
dispatch_sync(_cacheFileTasksQueue, purgeFilesBlock);
@@ -123,10 +144,10 @@ - (void)purgeCacheForKey:(NSString *)key andWait:(BOOL)wait {
[self purgeMemoryCacheForKey:key andWait:wait];
- NSString *cachedFilePath = [[self class] filePathForKey:key];
+ NSURL *cachedFileURL = [[self class] fileURLForKey:key];
void (^purgeFileBlock)(void) =^{
- [[NSFileManager defaultManager] removeItemAtPath:cachedFilePath error:nil];
+ [[NSFileManager defaultManager] removeItemAtURL:cachedFileURL error:nil];
};
if (YES == wait) {
@@ -142,4 +163,24 @@ - (void)purgeMemoryCacheForKey:(NSString *)key andWait:(BOOL)wait {
[_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
View
3  OGImage/__OGImage.m
@@ -89,8 +89,7 @@ - (id)initWithData:(NSData *)data scale:(CGFloat)scale {
// do we have an OGImageDictionary?
_originalFileType = (__bridge NSString *)CGImageSourceGetType(imageSource);
_originalFileAlphaInfo = CGImageGetAlphaInfo(cgImage);
- NSDictionary *propDict = CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL));
- _originalFileOrientation = [propDict[(__bridge NSString *)kCGImagePropertyOrientation] integerValue];
+ _originalFileOrientation = [_originalFileProperties[(__bridge NSString *)kCGImagePropertyOrientation] integerValue];
self = [super initWithCGImage:cgImage scale:scale orientation:OGEXIFOrientationToUIImageOrientation(_originalFileOrientation)];
CGImageRelease(cgImage);
}
View
12 OGImageDemo/OGImageDemo/OGAppDelegate.m
@@ -10,6 +10,7 @@
#import "OGViewController.h"
#import "DDLog.h"
#import "DDTTYLogger.h"
+#import "OGImageCache.h"
@implementation OGAppDelegate
@@ -29,25 +30,22 @@ - (void)setupLogging {
}
- (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 {
- // 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.
- // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+ // purge the disk cache of any image that hasn't been
+ // 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 {
- // 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 {
- // 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 {
- // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
@end
Please sign in to comment.
Something went wrong with that request. Please try again.