Skip to content

Commit a535c68

Browse files
authored
Update PersistableCache to use mapppings (#13)
* Update PersistableCache to use mapppings * Update test for macos and other os * Update sleep for potential flaky test
1 parent d6d6325 commit a535c68

File tree

4 files changed

+165
-26
lines changed

4 files changed

+165
-26
lines changed

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,13 @@ To use `PersistableCache`, make sure that the specified key type conforms to bot
117117
Here's an example of creating a cache, setting a value, and saving it to disk:
118118

119119
```swift
120-
let cache = PersistableCache<String, Double>()
120+
enum Key: String {
121+
case pi
122+
}
123+
124+
let cache = PersistableCache<Key, Double, Double>()
121125

122-
cache["pi"] = Double.pi
126+
cache[.pi] = Double.pi
123127

124128
do {
125129
try cache.save()
@@ -131,9 +135,9 @@ To use `PersistableCache`, make sure that the specified key type conforms to bot
131135
You can also load a previously saved cache from disk:
132136

133137
```swift
134-
let cache = PersistableCache<String, Double>()
138+
let cache = PersistableCache<Key, Double, Double>()
135139

136-
let pi = cache["pi"] // pi == Double.pi
140+
let pi = cache[.pi] // pi == Double.pi
137141
```
138142

139143
Remember that the `save()` function may throw errors if the encoder fails to serialize the cache to JSON or the disk write operation fails. Make sure to handle the errors appropriately.

Sources/Cache/Cache/PersistableCache.swift

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ import Foundation
99
Here's an example of creating a cache, setting a value, and saving it to disk:
1010

1111
```swift
12-
let cache = PersistableCache<String, Double>()
12+
enum Key: String {
13+
case pi
14+
}
15+
16+
let cache = PersistableCache<Key, Double, Double>()
1317

14-
cache["pi"] = Double.pi
18+
cache[.pi] = Double.pi
1519

1620
do {
1721
try cache.save()
@@ -23,9 +27,9 @@ import Foundation
2327
You can also load a previously saved cache from disk:
2428

2529
```swift
26-
let cache = PersistableCache<String, Double>()
30+
let cache = PersistableCache<Key, Double, Double>()
2731

28-
let pi = cache["pi"] // pi == Double.pi
32+
let pi = cache[.pi] // pi == Double.pi
2933
```
3034

3135
Note: You must make sure that the specified key type conforms to both `RawRepresentable` and `Hashable` protocols. The `RawValue` of `Key` must be a `String` type.
@@ -36,8 +40,8 @@ import Foundation
3640

3741
Make sure to handle the errors appropriately.
3842
*/
39-
open class PersistableCache<
40-
Key: RawRepresentable & Hashable, Value
43+
public class PersistableCache<
44+
Key: RawRepresentable & Hashable, Value, PersistedValue
4145
>: Cache<Key, Value> where Key.RawValue == String {
4246
private let lock: NSLock = NSLock()
4347

@@ -47,25 +51,36 @@ open class PersistableCache<
4751
/// The URL of the persistable cache file's directory.
4852
public let url: URL
4953

54+
private let persistedValueMap: (Value) -> PersistedValue?
55+
private let cachedValueMap: (PersistedValue) -> Value?
56+
5057
/**
5158
Loads a persistable cache with a specified name and URL.
5259

5360
- Parameters:
5461
- name: A string specifying the name of the cache.
5562
- url: A URL where the cache file directory will be or is stored.
63+
- persistedValueMap: A closure that maps the cached value to the `PersistedValue`.
64+
- cachedValueMap: A closure that maps the `PersistedValue` to`Value`.
5665
*/
5766
public init(
5867
name: String,
59-
url: URL
68+
url: URL,
69+
persistedValueMap: @escaping (Value) -> PersistedValue?,
70+
cachedValueMap: @escaping (PersistedValue) -> Value?
6071
) {
6172
self.name = name
6273
self.url = url
74+
self.persistedValueMap = persistedValueMap
75+
self.cachedValueMap = cachedValueMap
6376

6477
var initialValues: [Key: Value] = [:]
6578

6679
if let fileData = try? Data(contentsOf: url.fileURL(withName: name)) {
6780
let loadedJSON = JSON<Key>(data: fileData)
68-
initialValues = loadedJSON.values(ofType: Value.self)
81+
initialValues = loadedJSON
82+
.values(ofType: PersistedValue.self)
83+
.compactMapValues(cachedValueMap)
6984
}
7085

7186
super.init(initialValues: initialValues)
@@ -78,10 +93,12 @@ open class PersistableCache<
7893
*/
7994
public convenience init(
8095
name: String
81-
) {
96+
) where Value == PersistedValue {
8297
self.init(
8398
name: name,
84-
url: URL.defaultFileURL
99+
url: URL.defaultFileURL,
100+
persistedValueMap: { $0 },
101+
cachedValueMap: { $0 }
85102
)
86103
}
87104

@@ -90,14 +107,41 @@ open class PersistableCache<
90107

91108
- Parameter initialValues: A dictionary containing the initial cache contents.
92109
*/
93-
public required convenience init(initialValues: [Key: Value] = [:]) {
110+
public required convenience init(initialValues: [Key: Value] = [:]) where Value == PersistedValue {
94111
self.init(name: "\(Self.self)")
95112

96113
initialValues.forEach { key, value in
97114
set(value: value, forKey: key)
98115
}
99116
}
100117

118+
/**
119+
Loads the persistable cache with the given initial values. The `name` is set to `"\(Self.self)"`.
120+
121+
- Parameters:
122+
- initialValues: A dictionary containing the initial cache contents.
123+
- persistedValueMap: A closure that maps the cached value to the `PersistedValue`.
124+
- cachedValueMap: A closure that maps the `PersistedValue` to`Value`.
125+
*/
126+
public convenience init(
127+
initialValues: [Key: Value] = [:],
128+
persistedValueMap: @escaping (Value) -> PersistedValue?,
129+
cachedValueMap: @escaping (PersistedValue) -> Value?
130+
) {
131+
self.init(
132+
name: "\(Self.self)",
133+
url: URL.defaultFileURL,
134+
persistedValueMap: persistedValueMap,
135+
cachedValueMap: cachedValueMap
136+
)
137+
138+
initialValues.forEach { key, value in
139+
set(value: value, forKey: key)
140+
}
141+
}
142+
143+
required init(initialValues: [Key: Value] = [:]) { fatalError("init(initialValues:) has not been implemented") }
144+
101145
/**
102146
Saves the cache contents to disk.
103147

@@ -107,7 +151,8 @@ open class PersistableCache<
107151
*/
108152
public func save() throws {
109153
lock.lock()
110-
let json = JSON<Key>(initialValues: allValues)
154+
let persistedValues = allValues.compactMapValues(persistedValueMap)
155+
let json = JSON<Key>(initialValues: persistedValues)
111156
let data = try json.data()
112157
try data.write(to: url.fileURL(withName: name))
113158
lock.unlock()

Tests/CacheTests/ComposableCacheTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ final class ComposableCacheTests: XCTestCase {
4242
XCTAssertNotNil(cache.get(.c))
4343
XCTAssertNotNil(cache.get(.d))
4444

45-
sleep(1)
45+
sleep(2)
4646

4747
// Check ComposableCache
4848

Tests/CacheTests/PersistableCacheTests.swift

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
#if !os(Linux) && !os(Windows)
2+
#if os(macOS)
3+
import AppKit
4+
#else
5+
import UIKit
6+
#endif
7+
28
import XCTest
39
@testable import Cache
410

@@ -9,7 +15,7 @@ final class PersistableCacheTests: XCTestCase {
915
case author
1016
}
1117

12-
let cache: PersistableCache<Key, String> = PersistableCache(
18+
let cache: PersistableCache<Key, String, String> = PersistableCache(
1319
initialValues: [
1420
.text: "Hello, World!"
1521
]
@@ -24,13 +30,13 @@ final class PersistableCacheTests: XCTestCase {
2430
case text
2531
}
2632

27-
let failedLoadedCache: PersistableCache<SomeOtherKey, String> = PersistableCache()
33+
let failedLoadedCache: PersistableCache<SomeOtherKey, String, String> = PersistableCache()
2834

2935
XCTAssertEqual(failedLoadedCache.allValues.count, 0)
3036
XCTAssertEqual(failedLoadedCache.url, cache.url)
3137
XCTAssertNotEqual(failedLoadedCache.name, cache.name)
3238

33-
let loadedCache: PersistableCache<Key, String> = PersistableCache(
39+
let loadedCache: PersistableCache<Key, String, String> = PersistableCache(
3440
initialValues: [
3541
.author: "Leif"
3642
]
@@ -40,11 +46,11 @@ final class PersistableCacheTests: XCTestCase {
4046

4147
try loadedCache.delete()
4248

43-
let loadedDeletedCache: PersistableCache<Key, String> = PersistableCache()
49+
let loadedDeletedCache: PersistableCache<Key, String, String> = PersistableCache()
4450

4551
XCTAssertEqual(loadedDeletedCache.allValues.count, 0)
4652

47-
let expectedName = "PersistableCache<Key, String>"
53+
let expectedName = "PersistableCache<Key, String, String>"
4854
let expectedURL = FileManager.default.urls(
4955
for: .documentDirectory,
5056
in: .userDomainMask
@@ -67,15 +73,15 @@ final class PersistableCacheTests: XCTestCase {
6773
case author
6874
}
6975

70-
let cache: PersistableCache<Key, String> = PersistableCache(name: "test")
76+
let cache: PersistableCache<Key, String, String> = PersistableCache(name: "test")
7177

7278
cache[.text] = "Hello, World!"
7379

7480
XCTAssertEqual(cache.allValues.count, 1)
7581

7682
try cache.save()
7783

78-
let loadedCache: PersistableCache<Key, String> = PersistableCache(name: "test")
84+
let loadedCache: PersistableCache<Key, String, String> = PersistableCache(name: "test")
7985

8086
loadedCache[.author] = "Leif"
8187

@@ -87,7 +93,7 @@ final class PersistableCacheTests: XCTestCase {
8793
case text
8894
}
8995

90-
let otherKeyedLoadedCache: PersistableCache<SomeOtherKey, String> = PersistableCache(name: "test")
96+
let otherKeyedLoadedCache: PersistableCache<SomeOtherKey, String, String> = PersistableCache(name: "test")
9197

9298
XCTAssertEqual(otherKeyedLoadedCache.allValues.count, 1)
9399
XCTAssertEqual(otherKeyedLoadedCache.url, cache.url)
@@ -97,7 +103,7 @@ final class PersistableCacheTests: XCTestCase {
97103

98104
try loadedCache.delete()
99105

100-
let loadedDeletedCache: PersistableCache<Key, String> = PersistableCache(name: "test")
106+
let loadedDeletedCache: PersistableCache<Key, String, String> = PersistableCache(name: "test")
101107

102108
XCTAssertEqual(loadedDeletedCache.allValues.count, 0)
103109

@@ -117,5 +123,89 @@ final class PersistableCacheTests: XCTestCase {
117123
[URL](repeating: expectedURL, count: 4)
118124
)
119125
}
126+
127+
#if os(macOS)
128+
func testImage() throws {
129+
enum Key: String {
130+
case image
131+
}
132+
133+
let cache: PersistableCache<Key, NSImage, String> = PersistableCache(
134+
initialValues: [
135+
.image: try XCTUnwrap(NSImage(systemSymbolName: "circle", accessibilityDescription: nil))
136+
],
137+
persistedValueMap: { image in
138+
image.tiffRepresentation?.base64EncodedString()
139+
},
140+
cachedValueMap: { string in
141+
guard let data = Data(base64Encoded: string) else {
142+
return nil
143+
}
144+
145+
return NSImage(data: data)
146+
}
147+
)
148+
149+
XCTAssertEqual(cache.allValues.count, 1)
150+
151+
try cache.save()
152+
153+
let loadedCache: PersistableCache<Key, NSImage, String> = PersistableCache(
154+
persistedValueMap: { image in
155+
image.tiffRepresentation?.base64EncodedString()
156+
},
157+
cachedValueMap: { string in
158+
guard let data = Data(base64Encoded: string) else {
159+
return nil
160+
}
161+
162+
return NSImage(data: data)
163+
}
164+
)
165+
166+
XCTAssertEqual(loadedCache.allValues.count, 1)
167+
}
168+
#else
169+
func testImage() throws {
170+
enum Key: String {
171+
case image
172+
}
173+
174+
let cache: PersistableCache<Key, UIImage, String> = PersistableCache(
175+
initialValues: [
176+
.image: try XCTUnwrap(UIImage(systemName: "circle"))
177+
],
178+
persistedValueMap: { image in
179+
image.pngData()?.base64EncodedString()
180+
},
181+
cachedValueMap: { string in
182+
guard let data = Data(base64Encoded: string) else {
183+
return nil
184+
}
185+
186+
return UIImage(data: data)
187+
}
188+
)
189+
190+
XCTAssertEqual(cache.allValues.count, 1)
191+
192+
try cache.save()
193+
194+
let loadedCache: PersistableCache<Key, UIImage, String> = PersistableCache(
195+
persistedValueMap: { image in
196+
image.pngData()?.base64EncodedString()
197+
},
198+
cachedValueMap: { string in
199+
guard let data = Data(base64Encoded: string) else {
200+
return nil
201+
}
202+
203+
return UIImage(data: data)
204+
}
205+
)
206+
207+
XCTAssertEqual(loadedCache.allValues.count, 1)
208+
}
209+
#endif
120210
}
121211
#endif

0 commit comments

Comments
 (0)