From c43de6d4bd283f4cf2d561be809f392adbeeb09b Mon Sep 17 00:00:00 2001
From: Daryll Herberger <daryllh@vertigo.com>
Date: Wed, 16 Jul 2014 14:17:46 -0700
Subject: [PATCH 1/4] Adding selectors to allow disk-only cache insertion; this
 is handy for pre-caching objects from network responses that may not be
 immediately used

---
 SAMCache/SAMCache.h | 26 ++++++++++++++++++++++++--
 SAMCache/SAMCache.m | 38 ++++++++++++++++++++++++--------------
 2 files changed, 48 insertions(+), 16 deletions(-)

diff --git a/SAMCache/SAMCache.h b/SAMCache/SAMCache.h
index 5221b9e..40e5903 100755
--- a/SAMCache/SAMCache.h
+++ b/SAMCache/SAMCache.h
@@ -105,7 +105,7 @@
 ///----------------------------------------
 
 /**
- Synchronously set an object in the cache for a given key.
+ Synchronously set an object in the cache for a given key. Uses both memory and disk cache.
 
  @param object The object to store in the cache.
 
@@ -113,6 +113,17 @@
  */
 - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key;
 
+/**
+ Synchronously set an object in the cache for a given key.
+ 
+ @param object The object to store in the cache.
+ 
+ @param key The key of the object.
+ 
+ @param useDiskCacheOnly A value indicating whether or not to store the object in memory or only write object to disk cache location.
+ */
+- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key diskCacheOnly:(BOOL)useDiskCacheOnly;
+
 /**
  Remove an object from the cache.
 
@@ -203,7 +214,7 @@
 - (void)imageForKey:(NSString *)key usingBlock:(void (^)(UIImage *image))block;
 
 /**
- Synchronously store a PNG representation of an image in the cache for a given key.
+ Synchronously store a PNG representation of an image in the cache for a given key. Uses both memory and disk cache.
 
  @param image The image to store in the cache.
 
@@ -211,6 +222,17 @@
  */
 - (void)setImage:(UIImage *)image forKey:(NSString *)key;
 
+/**
+ Synchronously store a PNG representation of an image in the cache for a given key.
+ 
+ @param image The image to store in the cache.
+ 
+ @param key The key of the image.
+ 
+ @param useDiskCacheOnly A value indicating whether or not to store the object in memory or only write object to disk cache location.
+ */
+- (void)setImage:(UIImage *)image forKey:(NSString *)key diskCacheOnly:(BOOL)useDiskCacheOnly;
+
 /**
  Synchronously check if an image exists in the cache without retriving it.
 
diff --git a/SAMCache/SAMCache.m b/SAMCache/SAMCache.m
index 0c54b2e..4f5ef48 100755
--- a/SAMCache/SAMCache.m
+++ b/SAMCache/SAMCache.m
@@ -166,24 +166,29 @@ - (BOOL)objectExistsForKey:(NSString *)key {
 #pragma mark - Adding and Removing Cached Values
 
 - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key {
-	NSParameterAssert(key);
+	[self setObject:object forKey:key diskCacheOnly:NO];
+}
 
+- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key diskCacheOnly:(BOOL)useDiskCacheOnly {
+    NSParameterAssert(key);
+    
 	// If there's no object, delete the key.
 	if (!object) {
 		[self removeObjectForKey:key];
 		return;
 	}
-
-	// Save to memory cache
-	[self.cache setObject:object forKey:key];
-
+    
+    if (useDiskCacheOnly == NO) {
+        // Save to memory cache
+        [self.cache setObject:object forKey:key];
+    }
+    
 	dispatch_async(self.diskQueue, ^{
 		// Save to disk cache
 		[NSKeyedArchiver archiveRootObject:object toFile:[self _pathForKey:key]];
 	});
 }
 
-
 - (void)removeObjectForKey:(NSString *)key {
 	NSParameterAssert(key);
 
@@ -319,28 +324,33 @@ - (void)imageForKey:(NSString *)key usingBlock:(void (^)(UIImage *image))block {
 
 
 - (void)setImage:(UIImage *)image forKey:(NSString *)key {
-	NSParameterAssert(key);
+	[self setImage:image forKey:key diskCacheOnly:NO];
+}
 
+- (void)setImage:(UIImage *)image forKey:(NSString *)key diskCacheOnly:(BOOL)useDiskCacheOnly {
+    NSParameterAssert(key);
+    
 	// If there's no image, delete the key.
 	if (!image) {
 		[self removeObjectForKey:key];
 		return;
 	}
-
+    
 	key = [[self class] _keyForImageKey:key];
-
+    
 	dispatch_async(self.diskQueue, ^{
 		NSString *path = [self _pathForKey:key];
-
-		// Save to memory cache
-		[self.cache setObject:image forKey:key];
-
+        
+        if (useDiskCacheOnly == NO) {
+            // Save to memory cache
+            [self.cache setObject:image forKey:key];
+        }
+		
 		// Save to disk cache
 		[UIImagePNGRepresentation(image) writeToFile:path atomically:YES];
 	});
 }
 
-
 - (BOOL)imageExistsForKey:(NSString *)key {
 	NSParameterAssert(key);
 	return [self objectExistsForKey:[[self class] _keyForImageKey:key]];

From d3bdbea3badafa3b71e966c63efc70c0000f2be9 Mon Sep 17 00:00:00 2001
From: Daryll Herberger <daryllh@vertigo.com>
Date: Wed, 16 Jul 2014 18:32:16 -0700
Subject: [PATCH 2/4] Removing call to delete cache directory for a SAMCache
 named instance -- calling this selector appears to remove the cache directory
 entirely, resulting in failure to make subsequent insertions into the
 properly named subfolder on disk

---
 SAMCache/SAMCache.m | 1 -
 1 file changed, 1 deletion(-)

diff --git a/SAMCache/SAMCache.m b/SAMCache/SAMCache.m
index c007286..f1fb33e 100644
--- a/SAMCache/SAMCache.m
+++ b/SAMCache/SAMCache.m
@@ -202,7 +202,6 @@ - (void)removeAllObjects {
 		for (NSString *path in [self.fileManager contentsOfDirectoryAtPath:self.directory error:nil]) {
 			[self.fileManager removeItemAtPath:[self.directory stringByAppendingPathComponent:path] error:nil];
 		}
-		[self.fileManager removeItemAtPath:self.directory error:nil];
 	});
 }
 

From 6d7098c50d726f943bca9f64f4c199d217ddf790 Mon Sep 17 00:00:00 2001
From: Daryll Herberger <daryllh@vertigo.com>
Date: Mon, 6 Oct 2014 13:53:56 -0700
Subject: [PATCH 3/4] Fixing comments for setObject selectors to clarify that
 the disk cache saving operation is asynchronous, adding selector to allow for
 disk queue writing completion callback

---
 SAMCache/SAMCache.h | 19 ++++++++++++++++---
 SAMCache/SAMCache.m | 10 +++++++++-
 2 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/SAMCache/SAMCache.h b/SAMCache/SAMCache.h
index 40e5903..3ae8313 100755
--- a/SAMCache/SAMCache.h
+++ b/SAMCache/SAMCache.h
@@ -105,7 +105,7 @@
 ///----------------------------------------
 
 /**
- Synchronously set an object in the cache for a given key. Uses both memory and disk cache.
+ Synchronously set an object in the memory cache for a given key, while asynchronously writing to the disk cache. Uses both memory and disk cache.
 
  @param object The object to store in the cache.
 
@@ -114,16 +114,29 @@
 - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key;
 
 /**
- Synchronously set an object in the cache for a given key.
+ Synchronously set an object in the memory cache for a given key, while asynchronously writing to the disk cache. Has an option to only write asynchronously to the disk cache.
  
  @param object The object to store in the cache.
  
  @param key The key of the object.
  
- @param useDiskCacheOnly A value indicating whether or not to store the object in memory or only write object to disk cache location.
+ @param useDiskCacheOnly A value indicating whether or not to store the object in memory or only write object to disk cache location in an asynchronous fashion.
  */
 - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key diskCacheOnly:(BOOL)useDiskCacheOnly;
 
+/**
+ Synchronously set an object in the memory cache for a given key, while asynchronously writing to the disk cache. Has an option to only write asynchronously to the disk cache, along with a completion block for the asynchronous write operation.
+ 
+ @param object The object to store in the cache.
+ 
+ @param key The key of the object.
+ 
+ @param useDiskCacheOnly A value indicating whether or not to store the object in memory or only write object to disk cache location in an asynchronous fashion.
+ 
+ @param withCompletion A callback block that indicates that all operations (synchronous and asynchronous) have been completed, with an indication of success for the disk wrigin operation.
+ */
+- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key diskCacheOnly:(BOOL)useDiskCacheOnly withCompletion:(void (^)(BOOL didSave))block;
+
 /**
  Remove an object from the cache.
 
diff --git a/SAMCache/SAMCache.m b/SAMCache/SAMCache.m
index 2a80ee4..1e0d133 100755
--- a/SAMCache/SAMCache.m
+++ b/SAMCache/SAMCache.m
@@ -170,6 +170,10 @@ - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key {
 }
 
 - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key diskCacheOnly:(BOOL)useDiskCacheOnly {
+    [self setObject:object forKey:key diskCacheOnly:NO withCompletion:nil];
+}
+
+- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key diskCacheOnly:(BOOL)useDiskCacheOnly withCompletion:(void (^)(BOOL didSave))block {
     NSParameterAssert(key);
     
 	// If there's no object, delete the key.
@@ -185,7 +189,11 @@ - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key diskCacheOnly:(BO
     
 	dispatch_async(self.diskQueue, ^{
 		// Save to disk cache
-		[NSKeyedArchiver archiveRootObject:object toFile:[self _pathForKey:key]];
+		BOOL didSave = [NSKeyedArchiver archiveRootObject:object toFile:[self _pathForKey:key]];
+        
+        if (block) {
+            block(didSave);
+        }
 	});
 }
 

From b534939bf4a89c59d6147a9c3d7994468c151c49 Mon Sep 17 00:00:00 2001
From: Daryll Herberger <daryllh@vertigo.com>
Date: Thu, 20 Nov 2014 11:36:24 -0800
Subject: [PATCH 4/4] Adding selector for 'flushMemoryCache' -- useful for
 clearing the memory cache while keeping the disk cache intact, added unit
 test coverage for the new selectors

---
 SAMCache/SAMCache.h   | 13 +++++++++----
 SAMCache/SAMCache.m   | 19 +++++++++++++------
 Tests/SAMCacheTests.m | 36 ++++++++++++++++++++++++++++++++++++
 3 files changed, 58 insertions(+), 10 deletions(-)

diff --git a/SAMCache/SAMCache.h b/SAMCache/SAMCache.h
index 3ae8313..649ee7d 100755
--- a/SAMCache/SAMCache.h
+++ b/SAMCache/SAMCache.h
@@ -6,7 +6,7 @@
 //  Copyright (c) 2011-2014 Sam Soffes. All rights reserved.
 //
 
-#import <Foundation/Foundation.h>
+@import Foundation;
 
 @interface SAMCache : NSObject
 
@@ -133,9 +133,9 @@
  
  @param useDiskCacheOnly A value indicating whether or not to store the object in memory or only write object to disk cache location in an asynchronous fashion.
  
- @param withCompletion A callback block that indicates that all operations (synchronous and asynchronous) have been completed, with an indication of success for the disk wrigin operation.
+ @param completionBlock A callback block that indicates that all operations (synchronous and asynchronous) have been completed, with an indication of success for the disk wrigin operation.
  */
-- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key diskCacheOnly:(BOOL)useDiskCacheOnly withCompletion:(void (^)(BOOL didSave))block;
+- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key diskCacheOnly:(BOOL)useDiskCacheOnly withCompletion:(void (^)(BOOL didSave))completionBlock;
 
 /**
  Remove an object from the cache.
@@ -149,6 +149,11 @@
  */
 - (void)removeAllObjects;
 
+/**
+ Removes all cached objects from the memory cache. Does not remove disk-cache
+ entries.
+ */
+- (void)flushMemoryCache;
 
 ///-------------------------------
 /// @name Accessing the Disk Cache
@@ -195,7 +200,7 @@
 
 #if TARGET_OS_IPHONE
 
-#import <UIKit/UIImage.h>
+@import UIKit.UIImage;
 
 @interface SAMCache (UIImageAdditions)
 
diff --git a/SAMCache/SAMCache.m b/SAMCache/SAMCache.m
index 1e0d133..8334df5 100755
--- a/SAMCache/SAMCache.m
+++ b/SAMCache/SAMCache.m
@@ -170,10 +170,10 @@ - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key {
 }
 
 - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key diskCacheOnly:(BOOL)useDiskCacheOnly {
-    [self setObject:object forKey:key diskCacheOnly:NO withCompletion:nil];
+    [self setObject:object forKey:key diskCacheOnly:useDiskCacheOnly withCompletion:nil];
 }
 
-- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key diskCacheOnly:(BOOL)useDiskCacheOnly withCompletion:(void (^)(BOOL didSave))block {
+- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key diskCacheOnly:(BOOL)useDiskCacheOnly withCompletion:(void (^)(BOOL didSave))completionBlock {
     NSParameterAssert(key);
     
 	// If there's no object, delete the key.
@@ -187,12 +187,16 @@ - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key diskCacheOnly:(BO
         [self.cache setObject:object forKey:key];
     }
     
+    __weak SAMCache *weakSelf = self;
+    
 	dispatch_async(self.diskQueue, ^{
+        __strong SAMCache *strongSelf = weakSelf;
+        
 		// Save to disk cache
-		BOOL didSave = [NSKeyedArchiver archiveRootObject:object toFile:[self _pathForKey:key]];
+		BOOL didSave = [NSKeyedArchiver archiveRootObject:object toFile:[strongSelf _pathForKey:key]];
         
-        if (block) {
-            block(didSave);
+        if (completionBlock) {
+            completionBlock(didSave);
         }
 	});
 }
@@ -218,6 +222,9 @@ - (void)removeAllObjects {
 	});
 }
 
+- (void)flushMemoryCache {
+    [self.cache removeAllObjects];
+}
 
 #pragma mark - Accessing the Disk Cache
 
@@ -274,7 +281,7 @@ - (NSString *)_pathForKey:(NSString *)key {
 
 #if TARGET_OS_IPHONE
 
-#import <UIKit/UIScreen.h>
+@import UIKit.UIScreen;
 
 @implementation SAMCache (UIImageAdditions)
 
diff --git a/Tests/SAMCacheTests.m b/Tests/SAMCacheTests.m
index e021ba6..22a0fb4 100644
--- a/Tests/SAMCacheTests.m
+++ b/Tests/SAMCacheTests.m
@@ -69,4 +69,40 @@ - (void)testReadingWithSubscript {
 	XCTAssertEqualObjects(@"subread", self.cache[@"subscriptRead"], @"Reading an object with a subscript");
 }
 
+- (void)testAddingToDiskCacheOnly {
+    [self.cache setObject:@42 forKey:@"answer" diskCacheOnly:YES];
+    
+    XCTAssertNil([self.cache.cache objectForKey:@"answer"]);
+    
+    XCTAssertEqualObjects(@42, [self.cache objectForKey:@"answer"], @"Reading from disk cache");
+    
+    XCTAssertNotNil([self.cache.cache objectForKey:@"answer"]);
+}
+
+- (void)testAddingToDiskCacheOnlyWithCallback {
+    XCTestExpectation *diskWriteExpectation = [self expectationWithDescription:@"object written to disk"];
+    
+    __weak SAMCacheTests *weakSelf = self;
+    
+    [weakSelf.cache setObject:@42 forKey:@"answer" diskCacheOnly:YES withCompletion:^(BOOL didSave) {
+        XCTAssert(didSave);
+        [diskWriteExpectation fulfill];
+    }];
+    
+    [self waitForExpectationsWithTimeout:1 handler:nil];
+}
+
+- (void)testFlushMemoryCache {
+    [self.cache setObject:@42 forKey:@"answer"];
+    XCTAssertEqualObjects(@42, [self.cache objectForKey:@"answer"], @"Reading from memory cache");
+    
+    [self.cache flushMemoryCache];
+    
+    XCTAssertNil([self.cache.cache objectForKey:@"answer"]);
+    
+    XCTAssertEqualObjects(@42, [self.cache objectForKey:@"answer"], @"Reading from disk cache");
+    
+    XCTAssertNotNil([self.cache.cache objectForKey:@"answer"]);
+}
+
 @end