Skip to content

Commit a73bec5

Browse files
committed
Implement observations in HybridStorage
1 parent e42cbc0 commit a73bec5

File tree

5 files changed

+65
-20
lines changed

5 files changed

+65
-20
lines changed

Cache.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@
141141
D5A9D1BB211345D4005DBD3F /* Dictionary+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1BA211345D4005DBD3F /* Dictionary+Extensions.swift */; };
142142
D5A9D1BC211345D4005DBD3F /* Dictionary+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1BA211345D4005DBD3F /* Dictionary+Extensions.swift */; };
143143
D5A9D1BD211345D4005DBD3F /* Dictionary+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1BA211345D4005DBD3F /* Dictionary+Extensions.swift */; };
144+
D5A9D1BF21134776005DBD3F /* StoreChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1BE21134776005DBD3F /* StoreChange.swift */; };
145+
D5A9D1C021134776005DBD3F /* StoreChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1BE21134776005DBD3F /* StoreChange.swift */; };
146+
D5A9D1C121134776005DBD3F /* StoreChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1BE21134776005DBD3F /* StoreChange.swift */; };
144147
/* End PBXBuildFile section */
145148

146149
/* Begin PBXContainerItemProxy section */
@@ -230,6 +233,7 @@
230233
D5A138C31EB29C2100881A20 /* NSImage+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSImage+Extensions.swift"; sourceTree = "<group>"; };
231234
D5A9D1B621134547005DBD3F /* ObservationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservationToken.swift; sourceTree = "<group>"; };
232235
D5A9D1BA211345D4005DBD3F /* Dictionary+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+Extensions.swift"; sourceTree = "<group>"; };
236+
D5A9D1BE21134776005DBD3F /* StoreChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreChange.swift; sourceTree = "<group>"; };
233237
D5DC59E01C20593E003BD79B /* Cache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
234238
EBAACA991FBC369300FA206E /* SimpleStorage.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SimpleStorage.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
235239
/* End PBXFileReference section */
@@ -358,6 +362,7 @@
358362
D270147F20D10982003B45C7 /* Storage.swift */,
359363
D270148320D10E76003B45C7 /* AsyncStorage.swift */,
360364
D270148720D11040003B45C7 /* Storage+Transform.swift */,
365+
D5A9D1BE21134776005DBD3F /* StoreChange.swift */,
361366
);
362367
path = Storage;
363368
sourceTree = "<group>";
@@ -833,6 +838,7 @@
833838
D28897071F8B79B300C61DEE /* JSONDecoder+Extensions.swift in Sources */,
834839
D270148220D10982003B45C7 /* Storage.swift in Sources */,
835840
D221E5C220D00DCC00BC940E /* Entry.swift in Sources */,
841+
D5A9D1C121134776005DBD3F /* StoreChange.swift in Sources */,
836842
);
837843
runOnlyForDeploymentPostprocessing = 0;
838844
};
@@ -916,6 +922,7 @@
916922
D28897061F8B79B300C61DEE /* JSONDecoder+Extensions.swift in Sources */,
917923
D270148120D10982003B45C7 /* Storage.swift in Sources */,
918924
D221E5C120D00DCC00BC940E /* Entry.swift in Sources */,
925+
D5A9D1C021134776005DBD3F /* StoreChange.swift in Sources */,
919926
);
920927
runOnlyForDeploymentPostprocessing = 0;
921928
};
@@ -968,6 +975,7 @@
968975
D2CF98611F694FFA00CE8F68 /* MemoryConfig.swift in Sources */,
969976
D2CF98661F694FFA00CE8F68 /* ExpirationMode.swift in Sources */,
970977
D221E5C020D00DCC00BC940E /* Entry.swift in Sources */,
978+
D5A9D1BF21134776005DBD3F /* StoreChange.swift in Sources */,
971979
);
972980
runOnlyForDeploymentPostprocessing = 0;
973981
};

Source/Shared/Storage/HybridStorage.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ public class HybridStorage<T> {
55
public let memoryStorage: MemoryStorage<T>
66
public let diskStorage: DiskStorage<T>
77

8+
private var observations = [UUID : (HybridStorage, StoreChange) -> Void]()
9+
810
public init(memoryStorage: MemoryStorage<T>, diskStorage: DiskStorage<T>) {
911
self.memoryStorage = memoryStorage
1012
self.diskStorage = diskStorage
@@ -26,21 +28,25 @@ extension HybridStorage: StorageAware {
2628
public func removeObject(forKey key: String) throws {
2729
memoryStorage.removeObject(forKey: key)
2830
try diskStorage.removeObject(forKey: key)
31+
notifyObservers(of: .singleDeletion)
2932
}
3033

3134
public func setObject(_ object: T, forKey key: String, expiry: Expiry? = nil) throws {
3235
memoryStorage.setObject(object, forKey: key, expiry: expiry)
3336
try diskStorage.setObject(object, forKey: key, expiry: expiry)
37+
notifyObservers(of: .addition)
3438
}
3539

3640
public func removeAll() throws {
3741
memoryStorage.removeAll()
3842
try diskStorage.removeAll()
43+
notifyObservers(of: .allDeletion)
3944
}
4045

4146
public func removeExpiredObjects() throws {
4247
memoryStorage.removeExpiredObjects()
4348
try diskStorage.removeExpiredObjects()
49+
notifyObservers(of: .expiredDeletion)
4450
}
4551
}
4652

@@ -54,3 +60,25 @@ public extension HybridStorage {
5460
return storage
5561
}
5662
}
63+
64+
// MARK: - Observations
65+
66+
extension HybridStorage {
67+
@discardableResult
68+
func observeChanges(using closure: @escaping (HybridStorage, StoreChange) -> Void) -> ObservationToken {
69+
let id = observations.insert(closure)
70+
71+
return ObservationToken { [weak self] in
72+
self?.observations.removeValue(forKey: id)
73+
}
74+
}
75+
76+
private func notifyObservers(of change: StoreChange) {
77+
observations.values.forEach { [weak self] closure in
78+
guard let strongSelf = self else {
79+
return
80+
}
81+
closure(strongSelf, change)
82+
}
83+
}
84+
}

Source/Shared/Storage/Storage.swift

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
import Foundation
22
import Dispatch
33

4-
enum StoreChange {
5-
case addition
6-
case singleDeletion
7-
case allDeletion
8-
case expiredDeletion
9-
}
10-
114
/// Manage storage. Use memory storage if specified.
125
/// Synchronous by default. Use `async` for asynchronous operations.
136
public class Storage<T> {
@@ -41,7 +34,6 @@ public class Storage<T> {
4134
storage: hybridStorage,
4235
serialQueue: DispatchQueue(label: "Cache.SyncStorage.SerialQueue")
4336
)
44-
4537
let asyncStorage = AsyncStorage(
4638
storage: hybridStorage,
4739
serialQueue: DispatchQueue(label: "Cache.AsyncStorage.SerialQueue")
@@ -57,10 +49,29 @@ public class Storage<T> {
5749
public required init(syncStorage: SyncStorage<T>, asyncStorage: AsyncStorage<T>) {
5850
self.syncStorage = syncStorage
5951
self.asyncStorage = asyncStorage
52+
subscribeToChanges()
6053
}
6154

6255
/// Used for async operations
6356
public lazy var async = self.asyncStorage
57+
58+
private func subscribeToChanges() {
59+
subscribeToChanges(in: syncStorage.innerStorage)
60+
if syncStorage !== asyncStorage {
61+
subscribeToChanges(in: asyncStorage.innerStorage)
62+
}
63+
}
64+
65+
private func subscribeToChanges(in storage: HybridStorage<T>) {
66+
storage.observeChanges { [weak self] _, change in
67+
self?.observations.values.forEach { [weak self] closure in
68+
guard let strongSelf = self else {
69+
return
70+
}
71+
closure(strongSelf, change)
72+
}
73+
}
74+
}
6475
}
6576

6677
extension Storage: StorageAware {
@@ -74,26 +85,14 @@ extension Storage: StorageAware {
7485

7586
public func setObject(_ object: T, forKey key: String, expiry: Expiry? = nil) throws {
7687
try self.syncStorage.setObject(object, forKey: key, expiry: expiry)
77-
notifyObservers(of: .addition)
7888
}
7989

8090
public func removeAll() throws {
8191
try self.syncStorage.removeAll()
82-
notifyObservers(of: .allDeletion)
8392
}
8493

8594
public func removeExpiredObjects() throws {
8695
try self.syncStorage.removeExpiredObjects()
87-
notifyObservers(of: .expiredDeletion)
88-
}
89-
90-
private func notifyObservers(of change: StoreChange) {
91-
observations.values.forEach { [weak self] closure in
92-
guard let strongSelf = self else {
93-
return
94-
}
95-
closure(strongSelf, change)
96-
}
9796
}
9897
}
9998

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
public enum StoreChange {
2+
case addition
3+
case singleDeletion
4+
case allDeletion
5+
case expiredDeletion
6+
}

Tests/iOS/Tests/Storage/StorageTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,8 @@ final class StorageTests: XCTestCase {
9696
let cachedObject = try! alienStorage.object(forKey: "person")
9797
XCTAssertEqual(cachedObject.firstName, "John")
9898
}
99+
100+
func testObserveAddition() {
101+
102+
}
99103
}

0 commit comments

Comments
 (0)