Skip to content
This repository has been archived by the owner on Aug 24, 2019. It is now read-only.

Disk-only cache writing and ability to flush memory cache #14

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
48 changes: 44 additions & 4 deletions SAMCache/SAMCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright (c) 2011-2014 Sam Soffes. All rights reserved.
//

#import <Foundation/Foundation.h>
@import Foundation;

@interface SAMCache : NSObject

Expand Down Expand Up @@ -105,14 +105,38 @@
///----------------------------------------

/**
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. Uses both memory and disk cache.

@param object The object to store in the cache.

@param key The key of the object.
*/
- (void)setObject:(id <NSCoding>)object forKey:(NSString *)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 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 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))completionBlock;

/**
Remove an object from the cache.

Expand All @@ -125,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
Expand Down Expand Up @@ -171,7 +200,7 @@

#if TARGET_OS_IPHONE

#import <UIKit/UIImage.h>
@import UIKit.UIImage;

@interface SAMCache (UIImageAdditions)

Expand Down Expand Up @@ -203,14 +232,25 @@
- (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.

@param key The key of the image.
*/
- (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.

Expand Down
58 changes: 41 additions & 17 deletions SAMCache/SAMCache.m
Original file line number Diff line number Diff line change
Expand Up @@ -166,24 +166,41 @@ - (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 {
[self setObject:object forKey:key diskCacheOnly:useDiskCacheOnly withCompletion:nil];
}

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

__weak SAMCache *weakSelf = self;

dispatch_async(self.diskQueue, ^{
__strong SAMCache *strongSelf = weakSelf;

// Save to disk cache
[NSKeyedArchiver archiveRootObject:object toFile:[self _pathForKey:key]];
BOOL didSave = [NSKeyedArchiver archiveRootObject:object toFile:[strongSelf _pathForKey:key]];

if (completionBlock) {
completionBlock(didSave);
}
});
}


- (void)removeObjectForKey:(NSString *)key {
NSParameterAssert(key);

Expand All @@ -202,10 +219,12 @@ - (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];
});
}

- (void)flushMemoryCache {
[self.cache removeAllObjects];
}

#pragma mark - Accessing the Disk Cache

Expand Down Expand Up @@ -262,7 +281,7 @@ - (NSString *)_pathForKey:(NSString *)key {

#if TARGET_OS_IPHONE

#import <UIKit/UIScreen.h>
@import UIKit.UIScreen;

@implementation SAMCache (UIImageAdditions)

Expand Down Expand Up @@ -319,28 +338,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]];
Expand Down
36 changes: 36 additions & 0 deletions Tests/SAMCacheTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -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