Skip to content

Commit a7e40e7

Browse files
committed
Notify key observers when needed
1 parent 5e53be9 commit a7e40e7

File tree

7 files changed

+101
-33
lines changed

7 files changed

+101
-33
lines changed

Cache.xcodeproj/project.pbxproj

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,6 @@
145145
D5A9D1B721134547005DBD3F /* ObservationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1B621134547005DBD3F /* ObservationToken.swift */; };
146146
D5A9D1B821134547005DBD3F /* ObservationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1B621134547005DBD3F /* ObservationToken.swift */; };
147147
D5A9D1B921134547005DBD3F /* ObservationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1B621134547005DBD3F /* ObservationToken.swift */; };
148-
D5A9D1BF21134776005DBD3F /* StoreChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1BE21134776005DBD3F /* StoreChange.swift */; };
149-
D5A9D1C021134776005DBD3F /* StoreChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1BE21134776005DBD3F /* StoreChange.swift */; };
150-
D5A9D1C121134776005DBD3F /* StoreChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1BE21134776005DBD3F /* StoreChange.swift */; };
151148
D5A9D1C321144B65005DBD3F /* StorageObservationRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1C221144B65005DBD3F /* StorageObservationRegistry.swift */; };
152149
D5A9D1C421144B65005DBD3F /* StorageObservationRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1C221144B65005DBD3F /* StorageObservationRegistry.swift */; };
153150
D5A9D1C521144B65005DBD3F /* StorageObservationRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1C221144B65005DBD3F /* StorageObservationRegistry.swift */; };
@@ -242,7 +239,6 @@
242239
D5A138C01EB29BFA00881A20 /* UIImage+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = "<group>"; };
243240
D5A138C31EB29C2100881A20 /* NSImage+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSImage+Extensions.swift"; sourceTree = "<group>"; };
244241
D5A9D1B621134547005DBD3F /* ObservationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservationToken.swift; sourceTree = "<group>"; };
245-
D5A9D1BE21134776005DBD3F /* StoreChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreChange.swift; sourceTree = "<group>"; };
246242
D5A9D1C221144B65005DBD3F /* StorageObservationRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageObservationRegistry.swift; sourceTree = "<group>"; };
247243
D5DC59E01C20593E003BD79B /* Cache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
248244
EBAACA991FBC369300FA206E /* SimpleStorage.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SimpleStorage.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
@@ -371,7 +367,6 @@
371367
D270147F20D10982003B45C7 /* Storage.swift */,
372368
D270148320D10E76003B45C7 /* AsyncStorage.swift */,
373369
D270148720D11040003B45C7 /* Storage+Transform.swift */,
374-
D5A9D1BE21134776005DBD3F /* StoreChange.swift */,
375370
D5A9D1C221144B65005DBD3F /* StorageObservationRegistry.swift */,
376371
D51146522118337500197DCE /* KeyObservationRegistry.swift */,
377372
);
@@ -852,7 +847,6 @@
852847
D28897071F8B79B300C61DEE /* JSONDecoder+Extensions.swift in Sources */,
853848
D270148220D10982003B45C7 /* Storage.swift in Sources */,
854849
D221E5C220D00DCC00BC940E /* Entry.swift in Sources */,
855-
D5A9D1C121134776005DBD3F /* StoreChange.swift in Sources */,
856850
);
857851
runOnlyForDeploymentPostprocessing = 0;
858852
};
@@ -941,7 +935,6 @@
941935
D28897061F8B79B300C61DEE /* JSONDecoder+Extensions.swift in Sources */,
942936
D270148120D10982003B45C7 /* Storage.swift in Sources */,
943937
D221E5C120D00DCC00BC940E /* Entry.swift in Sources */,
944-
D5A9D1C021134776005DBD3F /* StoreChange.swift in Sources */,
945938
);
946939
runOnlyForDeploymentPostprocessing = 0;
947940
};
@@ -995,7 +988,6 @@
995988
D2CF98611F694FFA00CE8F68 /* MemoryConfig.swift in Sources */,
996989
D2CF98661F694FFA00CE8F68 /* ExpirationMode.swift in Sources */,
997990
D221E5C020D00DCC00BC940E /* Entry.swift in Sources */,
998-
D5A9D1BF21134776005DBD3F /* StoreChange.swift in Sources */,
999991
);
1000992
runOnlyForDeploymentPostprocessing = 0;
1001993
};

Source/Shared/Storage/DiskStorage.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ final public class DiskStorage<T> {
99
/// File manager to read/write to the disk
1010
public let fileManager: FileManager
1111
/// Configuration
12-
fileprivate let config: DiskConfig
12+
private let config: DiskConfig
1313
/// The computed path `directory+name`
1414
public let path: String
15+
/// The closure to be called when single file has been removed
16+
var onRemove: ((String) -> Void)?
1517

1618
private let transformer: Transformer<T>
19+
1720

1821
// MARK: - Initialization
1922

@@ -86,7 +89,9 @@ extension DiskStorage: StorageAware {
8689
}
8790

8891
public func removeObject(forKey key: String) throws {
89-
try fileManager.removeItem(atPath: makeFilePath(for: key))
92+
let filePath = makeFilePath(for: key)
93+
try fileManager.removeItem(atPath: filePath)
94+
onRemove?(filePath)
9095
}
9196

9297
public func removeAll() throws {
@@ -135,6 +140,7 @@ extension DiskStorage: StorageAware {
135140
// Remove expired objects
136141
for url in filesToDelete {
137142
try fileManager.removeItem(at: url)
143+
onRemove?(url.path)
138144
}
139145

140146
// Remove objects if storage size exceeds max size
@@ -220,9 +226,12 @@ extension DiskStorage {
220226

221227
for file in sortedFiles {
222228
try fileManager.removeItem(at: file.url)
229+
onRemove?(file.url.path)
230+
223231
if let fileSize = file.resourceValues.totalFileAllocatedSize {
224232
totalSize -= UInt(fileSize)
225233
}
234+
226235
if totalSize < targetSize {
227236
break
228237
}
@@ -238,6 +247,7 @@ extension DiskStorage {
238247
let attributes = try fileManager.attributesOfItem(atPath: filePath)
239248
if let expiryDate = attributes[.modificationDate] as? Date, expiryDate.inThePast {
240249
try fileManager.removeItem(atPath: filePath)
250+
onRemove?(filePath)
241251
}
242252
}
243253
}

Source/Shared/Storage/HybridStorage.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,22 @@ public final class HybridStorage<T> {
55
public let memoryStorage: MemoryStorage<T>
66
public let diskStorage: DiskStorage<T>
77
public let storageObservationRegistry = StorageObservationRegistry<HybridStorage>()
8+
public let keyObservationRegistry = KeyObservationRegistry<HybridStorage>()
89

910
public init(memoryStorage: MemoryStorage<T>, diskStorage: DiskStorage<T>) {
1011
self.memoryStorage = memoryStorage
1112
self.diskStorage = diskStorage
13+
14+
diskStorage.onRemove = { [weak self] path in
15+
self?.handleRemovedObject(at: path)
16+
}
17+
}
18+
19+
private func handleRemovedObject(at path: String) {
20+
keyObservationRegistry.notifyObserver(about: .remove, in: self) { key in
21+
let fileName = diskStorage.makeFileName(for: key)
22+
return path.contains(fileName)
23+
}
1224
}
1325
}
1426

@@ -27,24 +39,40 @@ extension HybridStorage: StorageAware {
2739
public func removeObject(forKey key: String) throws {
2840
memoryStorage.removeObject(forKey: key)
2941
try diskStorage.removeObject(forKey: key)
42+
3043
storageObservationRegistry.notifyObservers(about: .remove(key: key), in: self)
3144
}
3245

3346
public func setObject(_ object: T, forKey key: String, expiry: Expiry? = nil) throws {
47+
var keyChange: KeyChange<T>?
48+
49+
if !keyObservationRegistry.isEmpty {
50+
keyChange = .edit(before: try? self.object(forKey: key), after: object)
51+
}
52+
3453
memoryStorage.setObject(object, forKey: key, expiry: expiry)
3554
try diskStorage.setObject(object, forKey: key, expiry: expiry)
55+
56+
57+
if let change = keyChange {
58+
keyObservationRegistry.notifyObserver(forKey: key, about: change, in: self)
59+
}
60+
3661
storageObservationRegistry.notifyObservers(about: .add(key: key), in: self)
3762
}
3863

3964
public func removeAll() throws {
4065
memoryStorage.removeAll()
4166
try diskStorage.removeAll()
67+
4268
storageObservationRegistry.notifyObservers(about: .removeAll, in: self)
69+
keyObservationRegistry.notifyAllObservers(about: .remove, in: self)
4370
}
4471

4572
public func removeExpiredObjects() throws {
4673
memoryStorage.removeExpiredObjects()
4774
try diskStorage.removeExpiredObjects()
75+
4876
storageObservationRegistry.notifyObservers(about: .removeExpired, in: self)
4977
}
5078
}

Source/Shared/Storage/KeyObservationRegistry.swift

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import Foundation
22

3-
public enum KeyChange<T> {
4-
case edit(before: T?, after: T?)
5-
case remove
6-
}
7-
83
public final class KeyObservationRegistry<Storage: StorageAware> {
94
public typealias Observation = (Storage, KeyChange<Storage.T>) -> Void
105
private(set) var observations = [String: Observation]()
116

7+
public var isEmpty: Bool {
8+
return observations.isEmpty
9+
}
10+
1211
@discardableResult
1312
public func addObservation(_ observation: @escaping Observation, forKey key: String) -> ObservationToken {
1413
observations[key] = observation
@@ -30,9 +29,40 @@ public final class KeyObservationRegistry<Storage: StorageAware> {
3029
observations.removeAll()
3130
}
3231

33-
func notifyObservers(about change: KeyChange<Storage.T>, in storage: Storage) {
32+
func notifyObserver(forKey key: String, about change: KeyChange<Storage.T>, in storage: Storage) {
33+
observations[key]?(storage, change)
34+
}
35+
36+
func notifyObserver(about change: KeyChange<Storage.T>,
37+
in storage: Storage,
38+
where closure: ((String) -> Bool)) {
39+
let observation = observations.first { key, value in closure(key) }?.value
40+
observation?(storage, change)
41+
}
42+
43+
func notifyAllObservers(about change: KeyChange<Storage.T>, in storage: Storage) {
3444
observations.values.forEach { closure in
3545
closure(storage, change)
3646
}
3747
}
3848
}
49+
50+
// MARK: - KeyChange
51+
52+
public enum KeyChange<T> {
53+
case edit(before: T?, after: T)
54+
case remove
55+
}
56+
57+
extension KeyChange: Equatable where T: Equatable {
58+
public static func == (lhs: KeyChange<T>, rhs: KeyChange<T>) -> Bool {
59+
switch (lhs, rhs) {
60+
case (.edit(let before1, let after1), .edit(let before2, let after2)):
61+
return before1 == before2 && after1 == after2
62+
case (.remove, .remove):
63+
return true
64+
default:
65+
return false
66+
}
67+
}
68+
}

Source/Shared/Storage/Storage.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public final class Storage<T> {
99
let asyncStorage: AsyncStorage<T>
1010

1111
public let storageObservationRegistry = StorageObservationRegistry<Storage>()
12+
public let keyObservationRegistry = KeyObservationRegistry<Storage>()
1213

1314
/// Initialize storage with configuration options.
1415
///

Source/Shared/Storage/StorageObservationRegistry.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ public final class StorageObservationRegistry<Storage: StorageAware> {
44
public typealias Observation = (Storage, StorageChange) -> Void
55
private(set) var observations = [UUID: Observation]()
66

7+
public var isEmpty: Bool {
8+
return observations.isEmpty
9+
}
10+
711
@discardableResult
812
public func addObservation(_ observation: @escaping Observation) -> ObservationToken {
913
let id = UUID()
@@ -28,3 +32,23 @@ public final class StorageObservationRegistry<Storage: StorageAware> {
2832
}
2933
}
3034
}
35+
36+
// MARK: - StorageChange
37+
38+
public enum StorageChange: Equatable {
39+
case add(key: String)
40+
case remove(key: String)
41+
case removeAll
42+
case removeExpired
43+
}
44+
45+
public func == (lhs: StorageChange, rhs: StorageChange) -> Bool {
46+
switch (lhs, rhs) {
47+
case (.add(let key1), .add(let key2)), (.remove(let key1), .remove(let key2)):
48+
return key1 == key2
49+
case (.removeAll, .removeAll), (.removeExpired, .removeExpired):
50+
return true
51+
default:
52+
return false
53+
}
54+
}

Source/Shared/Storage/StoreChange.swift

Lines changed: 0 additions & 17 deletions
This file was deleted.

0 commit comments

Comments
 (0)