Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	Tests/iOS/Specs/Storage/DiskStorageSpec.swift
This commit is contained in:
Dmitry Letko
2017-05-21 20:17:07 +03:00
12 changed files with 265 additions and 38 deletions
+9 -1
View File
@@ -7,6 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
57506FAE1EC29437009B71E9 /* CacheEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57506FAD1EC29437009B71E9 /* CacheEntry.swift */; };
57506FBF1EC2E1B7009B71E9 /* CacheEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57506FAD1EC29437009B71E9 /* CacheEntry.swift */; };
57506FC01EC2E1BA009B71E9 /* CacheEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57506FAD1EC29437009B71E9 /* CacheEntry.swift */; };
BDEDD3601DBCE5CE007416A6 /* BasicHybridCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5291C6A1C2827FB00B702C9 /* BasicHybridCache.swift */; };
BDEDD3611DBCE5CE007416A6 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ACACDD1CD0272600567809 /* Cache.swift */; };
BDEDD3621DBCE5CE007416A6 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5291C151C28220B00B702C9 /* Config.swift */; };
@@ -143,6 +146,7 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
57506FAD1EC29437009B71E9 /* CacheEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheEntry.swift; sourceTree = "<group>"; };
BDEDD3561DBCE5B1007416A6 /* Cache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BDEDD3781DBCEB8A007416A6 /* Cache-tvOS-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Cache-tvOS-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
D5291C151C28220B00B702C9 /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = "<group>"; };
@@ -196,7 +200,7 @@
D5ACACCB1CD0207300567809 /* SyncHybridCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncHybridCache.swift; sourceTree = "<group>"; };
D5ACACCE1CD0227200567809 /* SyncCacheSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncCacheSpec.swift; sourceTree = "<group>"; };
D5ACACD21CD0254300567809 /* SyncHybridCacheSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncHybridCacheSpec.swift; sourceTree = "<group>"; };
D5ACACDD1CD0272600567809 /* Cache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = "<group>"; };
D5ACACDD1CD0272600567809 /* Cache.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = "<group>"; tabWidth = 2; };
D5DC59E01C20593E003BD79B /* Cache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
@@ -322,6 +326,7 @@
D5291C251C28220B00B702C9 /* MemoryStorage.swift */,
D5291C261C28220B00B702C9 /* StorageAware.swift */,
D5291C271C28220B00B702C9 /* StorageFactory.swift */,
57506FAD1EC29437009B71E9 /* CacheEntry.swift */,
);
path = Storage;
sourceTree = "<group>";
@@ -840,6 +845,7 @@
BDEDD36D1DBCE5D8007416A6 /* SyncCache.swift in Sources */,
BDEDD36A1DBCE5D8007416A6 /* NSDate+Cache.swift in Sources */,
BDEDD36F1DBCE5D8007416A6 /* DiskStorage.swift in Sources */,
57506FC01EC2E1BA009B71E9 /* CacheEntry.swift in Sources */,
D5A138C21EB29BFA00881A20 /* UIImage+Cache.swift in Sources */,
BDEDD3601DBCE5CE007416A6 /* BasicHybridCache.swift in Sources */,
BDEDD3701DBCE5D8007416A6 /* MemoryStorage.swift in Sources */,
@@ -922,6 +928,7 @@
D5291D911C283CFB00B702C9 /* String+Cache.swift in Sources */,
D5291D901C283CFB00B702C9 /* NSDate+Cache.swift in Sources */,
D5A138C41EB29C2100881A20 /* NSImage+Cache.swift in Sources */,
57506FBF1EC2E1B7009B71E9 /* CacheEntry.swift in Sources */,
D5291D931C283CFB00B702C9 /* DiskStorage.swift in Sources */,
D5291D861C283CFB00B702C9 /* BasicHybridCache.swift in Sources */,
D5ACACCD1CD0207300567809 /* SyncHybridCache.swift in Sources */,
@@ -960,6 +967,7 @@
D5291C321C28220B00B702C9 /* StorageKind.swift in Sources */,
D5291C301C28220B00B702C9 /* Expiry.swift in Sources */,
D5291C331C28220B00B702C9 /* JSON+Cache.swift in Sources */,
57506FAE1EC29437009B71E9 /* CacheEntry.swift in Sources */,
D5A138C11EB29BFA00881A20 /* UIImage+Cache.swift in Sources */,
D5291C2F1C28220B00B702C9 /* Capsule.swift in Sources */,
D5291C2D1C28220B00B702C9 /* Config.swift in Sources */,
+2 -2
View File
@@ -1,3 +1,3 @@
github "krzyzanowskim/CryptoSwift" "0.6.8"
github "Quick/Nimble" "v6.1.0"
github "Quick/Nimble" "v7.0.0"
github "Quick/Quick" "v1.1.0"
github "krzyzanowskim/CryptoSwift" "0.6.9"
+40 -15
View File
@@ -32,16 +32,22 @@ public class BasicHybridCache: NSObject {
- Parameter name: A name of the cache
- Parameter config: Cache configuration
*/
public init(name: String, config: Config = Config.defaultConfig) {
public convenience init(name: String, config: Config = Config.defaultConfig) {
let frontStorage = StorageFactory.resolve(name, kind: config.frontKind, maxSize: UInt(config.maxObjects))
let backStorage = StorageFactory.resolve(name, kind: config.backKind, maxSize: config.maxSize)
self.init(name: name, frontStorage: frontStorage, backStorage: backStorage, config: config)
}
internal init(name: String, frontStorage: StorageAware, backStorage: StorageAware, config: Config) {
self.name = name
self.frontStorage = frontStorage
self.backStorage = backStorage
self.config = config
frontStorage = StorageFactory.resolve(name, kind: config.frontKind, maxSize: UInt(config.maxObjects))
backStorage = StorageFactory.resolve(name, kind: config.backKind, maxSize: config.maxSize)
super.init()
let notificationCenter = NotificationCenter.default
#if os(macOS)
notificationCenter.addObserver(self, selector: #selector(clearExpiredDataInBackStorage),
name: NSNotification.Name.NSApplicationWillTerminate, object: nil)
@@ -56,7 +62,7 @@ public class BasicHybridCache: NSObject {
name: .UIApplicationDidEnterBackground, object: nil)
#endif
}
/**
Removes notification center observer.
*/
@@ -96,20 +102,39 @@ public class BasicHybridCache: NSObject {
- Parameter key: Unique key to identify the object in the cache
- Parameter completion: Completion closure returns object or nil
*/
func object<T: Cachable>(forKey key: String, completion: @escaping (_ object: T?) -> Void) {
frontStorage.object(key) { [weak self] (object: T?) in
if let object = object {
completion(object)
func object<T: Cachable>(forKey key: String, completion: @escaping (_ object: T?) -> Void){
cacheEntry(forKey: key) { (entry: CacheEntry<T>?) in
completion(entry?.object)
}
}
/**
Tries to retrieve the cache entry from to the front and back cache storages.
- Parameter key: Unique key to identify the cache entry in the cache
- Parameter completion: Completion closure returns cache entry or nil
*/
func cacheEntry<T: Cachable>(forKey key: String, completion: @escaping (_ object: CacheEntry<T>?) -> Void) {
frontStorage.cacheEntry(key) { [weak self] (entry: CacheEntry<T>?) in
if let entry = entry {
completion(entry)
return
}
guard let weakSelf = self else {
completion(object)
completion(entry)
return
}
weakSelf.backStorage.object(key) { (object: T?) in
completion(object)
weakSelf.backStorage.cacheEntry(key) { (entry: CacheEntry<T>?) in
guard let entry = entry else {
completion(nil)
return
}
weakSelf.frontStorage.add(key, object: entry.object, expiry: entry.expiry) { _ in
completion(entry)
}
}
}
}
+10 -1
View File
@@ -27,8 +27,17 @@ public final class Cache<T: Cachable>: BasicHybridCache {
- Parameter key: Unique key to identify the object in the cache
- Parameter completion: Completion closure returns object or nil
*/
public func object(_ key: String, completion: @escaping (_ object: T?) -> Void) {
super.object(forKey: key, completion: completion)
}
/**
Tries to retrieve the cache entry from to the front and back cache storages.
- Parameter key: Unique key to identify the cache entry in the cache
- Parameter completion: Completion closure returns cache entry or nil
*/
public func cacheEntry(_ key: String, completion: @escaping (_ object: CacheEntry<T>?) -> Void) {
super.cacheEntry(forKey: key, completion: completion)
}
}
+10
View File
@@ -26,4 +26,14 @@ public class HybridCache: BasicHybridCache {
public func object<T: Cachable>(_ key: String, completion: @escaping (_ object: T?) -> Void) {
super.object(forKey: key, completion: completion)
}
/**
Tries to retrieve the cache entry from to the front and back cache storages.
- Parameter key: Unique key to identify the cache entry in the cache
- Parameter completion: Completion closure returns cache entry or nil
*/
public func cacheEntry<T: Cachable>(_ key: String, completion: @escaping (_ object: CacheEntry<T>?) -> Void) {
super.cacheEntry(forKey: key, completion: completion)
}
}
+4
View File
@@ -0,0 +1,4 @@
public struct CacheEntry<T> {
public let object: T
public let expiry: Expiry
}
+39 -7
View File
@@ -87,30 +87,62 @@ public final class DiskStorage: StorageAware {
completion?()
}
}
/**
Tries to retrieve the object from the disk storage.
Gets information about the cached object.
- Parameter key: Unique key to identify the object in the cache
- Parameter completion: Completion closure returns object or nil
*/
public func object<T: Cachable>(_ key: String, completion: @escaping (_ object: T?) -> Void) {
cacheEntry(key) { (entry: CacheEntry<T>?) in
completion(entry?.object)
}
}
/**
Get cache entry which includes object with metadata.
- Parameter key: Unique key to identify the object in the cache
- Parameter completion: Completion closure returns object wrapper with metadata or nil
*/
public func cacheEntry<T: Cachable>(_ key: String, completion: @escaping (_ object: CacheEntry<T>?) -> Void) {
readQueue.async { [weak self] in
guard let weakSelf = self else {
completion(nil)
return
}
let filePath = weakSelf.filePath(key)
var cachedObject: T?
if let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) {
cachedObject = T.decode(data) as? T
}
completion(cachedObject)
if let cachedObject = cachedObject,
let expiry = weakSelf.cachedObjectExpiry(path: filePath) {
completion(CacheEntry(object: cachedObject, expiry: expiry))
return
}
completion(nil)
}
}
private func cachedObjectExpiry(path: String) -> Expiry? {
do {
let attributes = try fileManager.attributesOfItem(atPath: path)
guard let modificationDate = attributes[FileAttributeKey.modificationDate] as? Date else {
return nil
}
return Expiry.date(modificationDate)
} catch {}
return nil
}
/**
Removes the object from the cache by the given key.
+29 -8
View File
@@ -64,26 +64,47 @@ public final class MemoryStorage: StorageAware {
completion?()
}
}
/**
Tries to retrieve the object from the memory storage.
- Parameter key: Unique key to identify the object in the cache
- Parameter completion: Completion closure returns object or nil
*/
public func object<T: Cachable>(_ key: String, completion: @escaping (_ object: T?) -> Void) {
cacheEntry(key) { (entry: CacheEntry<T>?) in
completion(entry?.object)
}
}
/**
Get cache entry which includes object with metadata.
- Parameter key: Unique key to identify the object in the cache
- Parameter completion: Completion closure returns object wrapper with metadata or nil
*/
public func cacheEntry<T: Cachable>(_ key: String, completion: @escaping (_ object: CacheEntry<T>?) -> Void) {
readQueue.async { [weak self] in
guard let weakSelf = self else {
completion(nil)
return
}
let capsule = weakSelf.cache.object(forKey: key as AnyObject) as? Capsule
completion(capsule?.object as? T)
if let capsule = capsule {
weakSelf.removeIfExpired(key, capsule: capsule)
guard let capsule = weakSelf.cache.object(forKey: key as AnyObject) as? Capsule else {
completion(nil)
return
}
var entry: CacheEntry<T>?
if let object = capsule.object as? T {
entry = CacheEntry(object: object, expiry: Expiry.date(capsule.expiryDate))
}
completion(entry)
weakSelf.removeIfExpired(key, capsule: capsule)
}
}
+8
View File
@@ -15,6 +15,14 @@ public protocol CacheAware {
*/
func add<T: Cachable>(_ key: String, object: T, expiry: Expiry, completion: (() -> Void)?)
/**
Get cache entry which includes object with metadata.
- Parameter key: Unique key to identify the object in the cache
- Parameter completion: Completion closure returns object wrapper with metadata or nil
*/
func cacheEntry<T: Cachable>(_ key: String, completion: @escaping (_ object: CacheEntry<T>?) -> Void)
/**
Tries to retrieve the object from the cache.
+49 -3
View File
@@ -71,11 +71,29 @@ class CacheSpec: QuickSpec {
self.waitForExpectations(timeout: 8.0, handler:nil)
}
}
describe("#cacheEntry") {
it("resolves cache entry") {
waitUntil(timeout: 4.0) { done in
let expiryDate = Date()
cache.add(key, object: object, expiry: .date(expiryDate)) {
cache.cacheEntry(key) { (entry: CacheEntry<User>?) in
expect(entry?.object.firstName).to(equal(object.firstName))
expect(entry?.object.lastName).to(equal(object.lastName))
expect(entry?.expiry.date).to(equal(expiryDate))
done()
}
}
}
}
}
describe("#object") {
it("resolves cached object") {
let expectation = self.expectation(description: "Object Expectation")
cache.add(key, object: object) {
cache.object(key) { (receivedObject: User?) in
expect(receivedObject?.firstName).to(equal(object.firstName))
@@ -83,9 +101,37 @@ class CacheSpec: QuickSpec {
expectation.fulfill()
}
}
self.waitForExpectations(timeout: 4.0, handler:nil)
}
it("should resolve from disk and set in-memory cache if object not in-memory") {
let frontStorage = MemoryStorage(name: "MemoryStorage")
let backStorage = DiskStorage(name: "DiskStorage")
let config = Config.defaultConfig
let key = "myusernamedjohn"
let object = SpecHelper.user
let cache = Cache<User>(name: "MyCache", frontStorage: frontStorage, backStorage: backStorage, config: config)
waitUntil(timeout: 4.0) { done in
backStorage.add(key, object: object) {
cache.object(key) { (receivedObject: User?) in
expect(receivedObject?.firstName).to(equal(object.firstName))
expect(receivedObject?.lastName).to(equal(object.lastName))
frontStorage.object(key) { (inmemoryCachedUser: User?) in
expect(inmemoryCachedUser?.firstName).to(equal(object.firstName))
expect(inmemoryCachedUser?.lastName).to(equal(object.lastName))
done()
}
}
}
}
}
}
describe("#remove") {
@@ -67,6 +67,39 @@ class DiskStorageSpec: QuickSpec {
}
}
describe("#cacheEntry") {
it("returns nil if entry doesn't exist") {
let storage = DiskStorage(name: name)
waitUntil(timeout: 2.0) { done in
storage.cacheEntry(key) { (entry: CacheEntry<User>?) in
expect(entry).to(beNil())
done()
}
}
}
it("returns entry if object exists") {
let storage = DiskStorage(name: name)
waitUntil(timeout: 2.0) { done in
storage.add(key, object: object) {
storage.cacheEntry(key) { (entry: CacheEntry<User>?) in
let attributes = try! fileManager.attributesOfItem(atPath: storage.filePath(key))
let expiry = Expiry.date(attributes[FileAttributeKey.modificationDate] as! Date)
expect(entry?.object.firstName).to(equal(object.firstName))
expect(entry?.object.lastName).to(equal(object.lastName))
expect(entry?.expiry.date).to(equal(expiry.date))
done()
}
}
}
}
}
describe("#object") {
it("resolves cached object") {
let expectation = self.expectation(description: "Object Expectation")
@@ -47,7 +47,38 @@ class MemoryStorageSpec: QuickSpec {
self.waitForExpectations(timeout: 2.0, handler:nil)
}
}
describe("#cacheEntry") {
it("returns nil if entry doesn't exist") {
let storage = DiskStorage(name: name)
waitUntil(timeout: 2.0) { done in
storage.cacheEntry(key) { (entry: CacheEntry<User>?) in
expect(entry).to(beNil())
done()
}
}
}
it("returns entry if object exists") {
let storage = MemoryStorage(name: name)
let expiry = Expiry.date(Date())
waitUntil(timeout: 2.0) { done in
storage.add(key, object: object, expiry: expiry) {
storage.cacheEntry(key) { (entry: CacheEntry<User>?) in
expect(entry?.object.firstName).to(equal(object.firstName))
expect(entry?.object.lastName).to(equal(object.lastName))
expect(entry?.expiry.date).to(equal(expiry.date))
done()
}
}
}
}
}
describe("#object") {
it("resolves cached object") {
let expectation = self.expectation(description: "Object Expectation")