Skip to content

Commit

Permalink
Merge pull request #189 from hyperoslo/refactor/generic_storage
Browse files Browse the repository at this point in the history
Refactor to generic Storage
  • Loading branch information
onmyway133 authored Jun 13, 2018
2 parents c7fc68a + 47b19a1 commit 1432835
Show file tree
Hide file tree
Showing 27 changed files with 758 additions and 618 deletions.
238 changes: 138 additions & 100 deletions Cache.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

18 changes: 8 additions & 10 deletions Source/Shared/Configuration/MemoryConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,17 @@ public struct MemoryConfig {
/// Expiry date that will be applied by default for every added object
/// if it's not overridden in the add(key: object: expiry: completion:) method
public let expiry: Expiry
/// The maximum number of objects in memory the cache should hold. 0 means no limit.
/// The maximum number of objects in memory the cache should hold.
/// If 0, there is no count limit. The default value is 0.
public let countLimit: UInt

public init(expiry: Expiry = .never, countLimit: UInt = 0) {
self.expiry = expiry
self.countLimit = countLimit
}
/// The maximum total cost that the cache can hold before it starts evicting objects.
/// If 0, there is no total cost limit. The default value is 0
public let totalCostLimit: UInt

// MARK: - Deprecated
@available(*, deprecated,
message: "Use init(expiry:countLimit:) instead.",
renamed: "init(expiry:countLimit:)")
public init(expiry: Expiry = .never, countLimit: UInt = 0, totalCostLimit: UInt = 0) {
self.init(expiry: expiry, countLimit: countLimit)
self.expiry = expiry
self.countLimit = countLimit
self.totalCostLimit = totalCostLimit
}
}
2 changes: 1 addition & 1 deletion Source/Shared/Library/Entry.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// A wrapper around cached object and its expiry date.
public struct Entry<T: Codable> {
public struct Entry<T> {
/// Cached object
public let object: T
/// Expiry date
Expand Down
20 changes: 20 additions & 0 deletions Source/Shared/Library/MemoryCapsule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation

/// Helper class to hold cached instance and expiry date.
/// Used in memory storage to work with NSCache.
class MemoryCapsule: NSObject {
/// Object to be cached
let object: Any
/// Expiration date
let expiry: Expiry

/**
Creates a new instance of Capsule.
- Parameter value: Object to be cached
- Parameter expiry: Expiration date
*/
init(value: Any, expiry: Expiry) {
self.object = value
self.expiry = expiry
}
}
11 changes: 11 additions & 0 deletions Source/Shared/Library/Optional+Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

public extension Optional {
func unwrapOrThrow(error: Error) throws -> Wrapped {
if let value = self {
return value
} else {
throw error
}
}
}
2 changes: 2 additions & 0 deletions Source/Shared/Library/StorageError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public enum StorageError: Error {
case encodingFailed
/// The storage has been deallocated
case deallocated
/// Fail to perform transformation to or from Data
case transformerFail
}
11 changes: 11 additions & 0 deletions Source/Shared/Library/Transformer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

public class Transformer<T> {
let toData: (T) throws -> Data
let fromData: (Data) throws -> T

public init(toData: @escaping (T) throws -> Data, fromData: @escaping (Data) throws -> T) {
self.toData = toData
self.fromData = fromData
}
}
38 changes: 38 additions & 0 deletions Source/Shared/Library/TransformerFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Foundation

public class TransformerFactory {
public static func forData() -> Transformer<Data> {
let toData: (Data) throws -> Data = { $0 }

let fromData: (Data) throws -> Data = { $0 }

return Transformer<Data>(toData: toData, fromData: fromData)
}

public static func forImage() -> Transformer<Image> {
let toData: (Image) throws -> Data = { image in
return try image.cache_toData().unwrapOrThrow(error: StorageError.transformerFail)
}

let fromData: (Data) throws -> Image = { data in
return try Image(data: data).unwrapOrThrow(error: StorageError.transformerFail)
}

return Transformer<Image>(toData: toData, fromData: fromData)
}

public static func forCodable<U: Codable>(ofType: U.Type) -> Transformer<U> {
let toData: (U) throws -> Data = { object in
let wrapper = TypeWrapper<U>(object: object)
let encoder = JSONEncoder()
return try encoder.encode(wrapper)
}

let fromData: (Data) throws -> U = { data in
let decoder = JSONDecoder()
return try decoder.decode(TypeWrapper<U>.self, from: data).object
}

return Transformer<U>(toData: toData, fromData: fromData)
}
}
14 changes: 14 additions & 0 deletions Source/Shared/Library/TypeWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation

/// Used to wrap Codable object
public struct TypeWrapper<T: Codable>: Codable {
enum CodingKeys: String, CodingKey {
case object
}

public let object: T

public init(object: T) {
self.object = object
}
}
60 changes: 45 additions & 15 deletions Source/Shared/Storage/AsyncStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@ import Dispatch

/// Manipulate storage in a "all async" manner.
/// The completion closure will be called when operation completes.
public class AsyncStorage {
fileprivate let internalStorage: StorageAware
public class AsyncStorage<T> {
public let innerStorage: HybridStorage<T>
public let serialQueue: DispatchQueue

init(storage: StorageAware, serialQueue: DispatchQueue) {
self.internalStorage = storage
public init(storage: HybridStorage<T>, serialQueue: DispatchQueue) {
self.innerStorage = storage
self.serialQueue = serialQueue
}
}

extension AsyncStorage: AsyncStorageAware {
public func entry<T>(ofType type: T.Type, forKey key: String, completion: @escaping (Result<Entry<T>>) -> Void) {
extension AsyncStorage {
public func entry(forKey key: String, completion: @escaping (Result<Entry<T>>) -> Void) {
serialQueue.async { [weak self] in
guard let `self` = self else {
completion(Result.error(StorageError.deallocated))
return
}

do {
let anEntry = try self.internalStorage.entry(ofType: type, forKey: key)
let anEntry = try self.innerStorage.entry(forKey: key)
completion(Result.value(anEntry))
} catch {
completion(Result.error(error))
Expand All @@ -38,26 +38,27 @@ extension AsyncStorage: AsyncStorageAware {
}

do {
try self.internalStorage.removeObject(forKey: key)
try self.innerStorage.removeObject(forKey: key)
completion(Result.value(()))
} catch {
completion(Result.error(error))
}
}
}

public func setObject<T: Codable>(_ object: T,
forKey key: String,
expiry: Expiry? = nil,
completion: @escaping (Result<()>) -> Void) {
public func setObject(
_ object: T,
forKey key: String,
expiry: Expiry? = nil,
completion: @escaping (Result<()>) -> Void) {
serialQueue.async { [weak self] in
guard let `self` = self else {
completion(Result.error(StorageError.deallocated))
return
}

do {
try self.internalStorage.setObject(object, forKey: key, expiry: expiry)
try self.innerStorage.setObject(object, forKey: key, expiry: expiry)
completion(Result.value(()))
} catch {
completion(Result.error(error))
Expand All @@ -73,7 +74,7 @@ extension AsyncStorage: AsyncStorageAware {
}

do {
try self.internalStorage.removeAll()
try self.innerStorage.removeAll()
completion(Result.value(()))
} catch {
completion(Result.error(error))
Expand All @@ -89,11 +90,40 @@ extension AsyncStorage: AsyncStorageAware {
}

do {
try self.internalStorage.removeExpiredObjects()
try self.innerStorage.removeExpiredObjects()
completion(Result.value(()))
} catch {
completion(Result.error(error))
}
}
}

public func object(forKey key: String, completion: @escaping (Result<T>) -> Void) {
entry(forKey: key, completion: { (result: Result<Entry<T>>) in
completion(result.map({ entry in
return entry.object
}))
})
}

public func existsObject(
forKey key: String,
completion: @escaping (Result<Bool>) -> Void) {
object(forKey: key, completion: { (result: Result<T>) in
completion(result.map({ _ in
return true
}))
})
}
}

public extension AsyncStorage {
func transform<U>(transformer: Transformer<U>) -> AsyncStorage<U> {
let storage = AsyncStorage<U>(
storage: innerStorage.transform(transformer: transformer),
serialQueue: serialQueue
)

return storage
}
}
82 changes: 0 additions & 82 deletions Source/Shared/Storage/AsyncStorageAware.swift

This file was deleted.

Loading

0 comments on commit 1432835

Please sign in to comment.