Skip to content

Commit 1432835

Browse files
authored
Merge pull request #189 from hyperoslo/refactor/generic_storage
Refactor to generic Storage
2 parents c7fc68a + 47b19a1 commit 1432835

27 files changed

+758
-618
lines changed

Cache.xcodeproj/project.pbxproj

Lines changed: 138 additions & 100 deletions
Large diffs are not rendered by default.

Source/Shared/Configuration/MemoryConfig.swift

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,17 @@ public struct MemoryConfig {
44
/// Expiry date that will be applied by default for every added object
55
/// if it's not overridden in the add(key: object: expiry: completion:) method
66
public let expiry: Expiry
7-
/// The maximum number of objects in memory the cache should hold. 0 means no limit.
7+
/// The maximum number of objects in memory the cache should hold.
8+
/// If 0, there is no count limit. The default value is 0.
89
public let countLimit: UInt
910

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

15-
// MARK: - Deprecated
16-
@available(*, deprecated,
17-
message: "Use init(expiry:countLimit:) instead.",
18-
renamed: "init(expiry:countLimit:)")
1915
public init(expiry: Expiry = .never, countLimit: UInt = 0, totalCostLimit: UInt = 0) {
20-
self.init(expiry: expiry, countLimit: countLimit)
16+
self.expiry = expiry
17+
self.countLimit = countLimit
18+
self.totalCostLimit = totalCostLimit
2119
}
2220
}

Source/Shared/Library/Entry.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Foundation
22

33
/// A wrapper around cached object and its expiry date.
4-
public struct Entry<T: Codable> {
4+
public struct Entry<T> {
55
/// Cached object
66
public let object: T
77
/// Expiry date
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Foundation
2+
3+
/// Helper class to hold cached instance and expiry date.
4+
/// Used in memory storage to work with NSCache.
5+
class MemoryCapsule: NSObject {
6+
/// Object to be cached
7+
let object: Any
8+
/// Expiration date
9+
let expiry: Expiry
10+
11+
/**
12+
Creates a new instance of Capsule.
13+
- Parameter value: Object to be cached
14+
- Parameter expiry: Expiration date
15+
*/
16+
init(value: Any, expiry: Expiry) {
17+
self.object = value
18+
self.expiry = expiry
19+
}
20+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Foundation
2+
3+
public extension Optional {
4+
func unwrapOrThrow(error: Error) throws -> Wrapped {
5+
if let value = self {
6+
return value
7+
} else {
8+
throw error
9+
}
10+
}
11+
}

Source/Shared/Library/StorageError.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ public enum StorageError: Error {
1313
case encodingFailed
1414
/// The storage has been deallocated
1515
case deallocated
16+
/// Fail to perform transformation to or from Data
17+
case transformerFail
1618
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Foundation
2+
3+
public class Transformer<T> {
4+
let toData: (T) throws -> Data
5+
let fromData: (Data) throws -> T
6+
7+
public init(toData: @escaping (T) throws -> Data, fromData: @escaping (Data) throws -> T) {
8+
self.toData = toData
9+
self.fromData = fromData
10+
}
11+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Foundation
2+
3+
public class TransformerFactory {
4+
public static func forData() -> Transformer<Data> {
5+
let toData: (Data) throws -> Data = { $0 }
6+
7+
let fromData: (Data) throws -> Data = { $0 }
8+
9+
return Transformer<Data>(toData: toData, fromData: fromData)
10+
}
11+
12+
public static func forImage() -> Transformer<Image> {
13+
let toData: (Image) throws -> Data = { image in
14+
return try image.cache_toData().unwrapOrThrow(error: StorageError.transformerFail)
15+
}
16+
17+
let fromData: (Data) throws -> Image = { data in
18+
return try Image(data: data).unwrapOrThrow(error: StorageError.transformerFail)
19+
}
20+
21+
return Transformer<Image>(toData: toData, fromData: fromData)
22+
}
23+
24+
public static func forCodable<U: Codable>(ofType: U.Type) -> Transformer<U> {
25+
let toData: (U) throws -> Data = { object in
26+
let wrapper = TypeWrapper<U>(object: object)
27+
let encoder = JSONEncoder()
28+
return try encoder.encode(wrapper)
29+
}
30+
31+
let fromData: (Data) throws -> U = { data in
32+
let decoder = JSONDecoder()
33+
return try decoder.decode(TypeWrapper<U>.self, from: data).object
34+
}
35+
36+
return Transformer<U>(toData: toData, fromData: fromData)
37+
}
38+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Foundation
2+
3+
/// Used to wrap Codable object
4+
public struct TypeWrapper<T: Codable>: Codable {
5+
enum CodingKeys: String, CodingKey {
6+
case object
7+
}
8+
9+
public let object: T
10+
11+
public init(object: T) {
12+
self.object = object
13+
}
14+
}

Source/Shared/Storage/AsyncStorage.swift

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,26 @@ import Dispatch
33

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

10-
init(storage: StorageAware, serialQueue: DispatchQueue) {
11-
self.internalStorage = storage
10+
public init(storage: HybridStorage<T>, serialQueue: DispatchQueue) {
11+
self.innerStorage = storage
1212
self.serialQueue = serialQueue
1313
}
1414
}
1515

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

2424
do {
25-
let anEntry = try self.internalStorage.entry(ofType: type, forKey: key)
25+
let anEntry = try self.innerStorage.entry(forKey: key)
2626
completion(Result.value(anEntry))
2727
} catch {
2828
completion(Result.error(error))
@@ -38,26 +38,27 @@ extension AsyncStorage: AsyncStorageAware {
3838
}
3939

4040
do {
41-
try self.internalStorage.removeObject(forKey: key)
41+
try self.innerStorage.removeObject(forKey: key)
4242
completion(Result.value(()))
4343
} catch {
4444
completion(Result.error(error))
4545
}
4646
}
4747
}
4848

49-
public func setObject<T: Codable>(_ object: T,
50-
forKey key: String,
51-
expiry: Expiry? = nil,
52-
completion: @escaping (Result<()>) -> Void) {
49+
public func setObject(
50+
_ object: T,
51+
forKey key: String,
52+
expiry: Expiry? = nil,
53+
completion: @escaping (Result<()>) -> Void) {
5354
serialQueue.async { [weak self] in
5455
guard let `self` = self else {
5556
completion(Result.error(StorageError.deallocated))
5657
return
5758
}
5859

5960
do {
60-
try self.internalStorage.setObject(object, forKey: key, expiry: expiry)
61+
try self.innerStorage.setObject(object, forKey: key, expiry: expiry)
6162
completion(Result.value(()))
6263
} catch {
6364
completion(Result.error(error))
@@ -73,7 +74,7 @@ extension AsyncStorage: AsyncStorageAware {
7374
}
7475

7576
do {
76-
try self.internalStorage.removeAll()
77+
try self.innerStorage.removeAll()
7778
completion(Result.value(()))
7879
} catch {
7980
completion(Result.error(error))
@@ -89,11 +90,40 @@ extension AsyncStorage: AsyncStorageAware {
8990
}
9091

9192
do {
92-
try self.internalStorage.removeExpiredObjects()
93+
try self.innerStorage.removeExpiredObjects()
9394
completion(Result.value(()))
9495
} catch {
9596
completion(Result.error(error))
9697
}
9798
}
9899
}
100+
101+
public func object(forKey key: String, completion: @escaping (Result<T>) -> Void) {
102+
entry(forKey: key, completion: { (result: Result<Entry<T>>) in
103+
completion(result.map({ entry in
104+
return entry.object
105+
}))
106+
})
107+
}
108+
109+
public func existsObject(
110+
forKey key: String,
111+
completion: @escaping (Result<Bool>) -> Void) {
112+
object(forKey: key, completion: { (result: Result<T>) in
113+
completion(result.map({ _ in
114+
return true
115+
}))
116+
})
117+
}
118+
}
119+
120+
public extension AsyncStorage {
121+
func transform<U>(transformer: Transformer<U>) -> AsyncStorage<U> {
122+
let storage = AsyncStorage<U>(
123+
storage: innerStorage.transform(transformer: transformer),
124+
serialQueue: serialQueue
125+
)
126+
127+
return storage
128+
}
99129
}

0 commit comments

Comments
 (0)