From e42cbc06ffaab9dcc85a7fdef5650f67cfc4e5f1 Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Thu, 2 Aug 2018 16:02:46 +0200 Subject: [PATCH] Implement store observations --- Cache.xcodeproj/project.pbxproj | 16 ++++++++++ .../Extensions/Dictionary+Extensions.swift | 9 ++++++ Source/Shared/Library/ObservationToken.swift | 11 +++++++ Source/Shared/Storage/Storage.swift | 30 +++++++++++++++++++ 4 files changed, 66 insertions(+) create mode 100644 Source/Shared/Extensions/Dictionary+Extensions.swift create mode 100644 Source/Shared/Library/ObservationToken.swift diff --git a/Cache.xcodeproj/project.pbxproj b/Cache.xcodeproj/project.pbxproj index 8b77e37..a080615 100644 --- a/Cache.xcodeproj/project.pbxproj +++ b/Cache.xcodeproj/project.pbxproj @@ -135,6 +135,12 @@ D5A138C11EB29BFA00881A20 /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A138C01EB29BFA00881A20 /* UIImage+Extensions.swift */; }; D5A138C21EB29BFA00881A20 /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A138C01EB29BFA00881A20 /* UIImage+Extensions.swift */; }; D5A138C41EB29C2100881A20 /* NSImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A138C31EB29C2100881A20 /* NSImage+Extensions.swift */; }; + D5A9D1B721134547005DBD3F /* ObservationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1B621134547005DBD3F /* ObservationToken.swift */; }; + D5A9D1B821134547005DBD3F /* ObservationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1B621134547005DBD3F /* ObservationToken.swift */; }; + D5A9D1B921134547005DBD3F /* ObservationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1B621134547005DBD3F /* ObservationToken.swift */; }; + D5A9D1BB211345D4005DBD3F /* Dictionary+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1BA211345D4005DBD3F /* Dictionary+Extensions.swift */; }; + D5A9D1BC211345D4005DBD3F /* Dictionary+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1BA211345D4005DBD3F /* Dictionary+Extensions.swift */; }; + D5A9D1BD211345D4005DBD3F /* Dictionary+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9D1BA211345D4005DBD3F /* Dictionary+Extensions.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -222,6 +228,8 @@ D5643E361C43F2CC00582E17 /* Storage.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Storage.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; D5A138C01EB29BFA00881A20 /* UIImage+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = ""; }; D5A138C31EB29C2100881A20 /* NSImage+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSImage+Extensions.swift"; sourceTree = ""; }; + D5A9D1B621134547005DBD3F /* ObservationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservationToken.swift; sourceTree = ""; }; + D5A9D1BA211345D4005DBD3F /* Dictionary+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+Extensions.swift"; sourceTree = ""; }; D5DC59E01C20593E003BD79B /* Cache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cache.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EBAACA991FBC369300FA206E /* SimpleStorage.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SimpleStorage.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; /* End PBXFileReference section */ @@ -310,6 +318,7 @@ children = ( D28897041F8B79B300C61DEE /* JSONDecoder+Extensions.swift */, D2CF98501F694FFA00CE8F68 /* Date+Extensions.swift */, + D5A9D1BA211345D4005DBD3F /* Dictionary+Extensions.swift */, ); path = Extensions; sourceTree = ""; @@ -333,6 +342,7 @@ D270148F20D1251E003B45C7 /* TypeWrapper.swift */, D270149320D125AC003B45C7 /* MemoryCapsule.swift */, D27014A420D129EB003B45C7 /* TransformerFactory.swift */, + D5A9D1B621134547005DBD3F /* ObservationToken.swift */, ); path = Library; sourceTree = ""; @@ -806,12 +816,14 @@ D270147220D1018A003B45C7 /* Transformer.swift in Sources */, D21B668C1F6A723C00125DE1 /* Types.swift in Sources */, D2D4CC261FA3426B00E4A2D5 /* JSONArrayWrapper.swift in Sources */, + D5A9D1BD211345D4005DBD3F /* Dictionary+Extensions.swift in Sources */, D27014A720D129EB003B45C7 /* TransformerFactory.swift in Sources */, D270147E20D107DA003B45C7 /* SyncStorage.swift in Sources */, D292DAFF1F6A970B0060F614 /* Result.swift in Sources */, D21B669A1F6A724300125DE1 /* Date+Extensions.swift in Sources */, D21B66891F6A723C00125DE1 /* ImageWrapper.swift in Sources */, D21B668B1F6A723C00125DE1 /* StorageError.swift in Sources */, + D5A9D1B921134547005DBD3F /* ObservationToken.swift in Sources */, D270148A20D11040003B45C7 /* Storage+Transform.swift in Sources */, D5A138C21EB29BFA00881A20 /* UIImage+Extensions.swift in Sources */, D21B66851F6A723C00125DE1 /* DataSerializer.swift in Sources */, @@ -887,12 +899,14 @@ D270147120D1018A003B45C7 /* Transformer.swift in Sources */, D21B66831F6A723C00125DE1 /* Types.swift in Sources */, D2D4CC251FA3426B00E4A2D5 /* JSONArrayWrapper.swift in Sources */, + D5A9D1BC211345D4005DBD3F /* Dictionary+Extensions.swift in Sources */, D27014A620D129EB003B45C7 /* TransformerFactory.swift in Sources */, D270147D20D107DA003B45C7 /* SyncStorage.swift in Sources */, D292DAFE1F6A970B0060F614 /* Result.swift in Sources */, D21B66991F6A724200125DE1 /* Date+Extensions.swift in Sources */, D21B66801F6A723C00125DE1 /* ImageWrapper.swift in Sources */, D21B66821F6A723C00125DE1 /* StorageError.swift in Sources */, + D5A9D1B821134547005DBD3F /* ObservationToken.swift in Sources */, D270148920D11040003B45C7 /* Storage+Transform.swift in Sources */, D5A138C41EB29C2100881A20 /* NSImage+Extensions.swift in Sources */, D21B667C1F6A723C00125DE1 /* DataSerializer.swift in Sources */, @@ -937,12 +951,14 @@ D2CF98601F694FFA00CE8F68 /* DiskConfig.swift in Sources */, D270147020D1018A003B45C7 /* Transformer.swift in Sources */, D292DAFD1F6A970B0060F614 /* Result.swift in Sources */, + D5A9D1BB211345D4005DBD3F /* Dictionary+Extensions.swift in Sources */, D27014A520D129EB003B45C7 /* TransformerFactory.swift in Sources */, D2D4CC241FA3426B00E4A2D5 /* JSONArrayWrapper.swift in Sources */, D270147C20D107DA003B45C7 /* SyncStorage.swift in Sources */, D2CF98671F694FFA00CE8F68 /* Expiry.swift in Sources */, D270148820D11040003B45C7 /* Storage+Transform.swift in Sources */, D2CF986A1F694FFA00CE8F68 /* StorageError.swift in Sources */, + D5A9D1B721134547005DBD3F /* ObservationToken.swift in Sources */, D5A138C11EB29BFA00881A20 /* UIImage+Extensions.swift in Sources */, D270147820D1046A003B45C7 /* HybridStorage.swift in Sources */, D270148420D10E76003B45C7 /* AsyncStorage.swift in Sources */, diff --git a/Source/Shared/Extensions/Dictionary+Extensions.swift b/Source/Shared/Extensions/Dictionary+Extensions.swift new file mode 100644 index 0000000..9b0cd49 --- /dev/null +++ b/Source/Shared/Extensions/Dictionary+Extensions.swift @@ -0,0 +1,9 @@ +import Foundation + +extension Dictionary where Key == UUID { + mutating func insert(_ value: Value) -> UUID { + let id = UUID() + self[id] = value + return id + } +} diff --git a/Source/Shared/Library/ObservationToken.swift b/Source/Shared/Library/ObservationToken.swift new file mode 100644 index 0000000..756a3f7 --- /dev/null +++ b/Source/Shared/Library/ObservationToken.swift @@ -0,0 +1,11 @@ +final class ObservationToken { + private let cancellationClosure: () -> Void + + init(cancellationClosure: @escaping () -> Void) { + self.cancellationClosure = cancellationClosure + } + + func cancel() { + cancellationClosure() + } +} diff --git a/Source/Shared/Storage/Storage.swift b/Source/Shared/Storage/Storage.swift index 2f74492..7218d4e 100644 --- a/Source/Shared/Storage/Storage.swift +++ b/Source/Shared/Storage/Storage.swift @@ -1,6 +1,13 @@ import Foundation import Dispatch +enum StoreChange { + case addition + case singleDeletion + case allDeletion + case expiredDeletion +} + /// Manage storage. Use memory storage if specified. /// Synchronous by default. Use `async` for asynchronous operations. public class Storage { @@ -8,6 +15,17 @@ public class Storage { let syncStorage: SyncStorage let asyncStorage: AsyncStorage + private var observations = [UUID : (Storage, StoreChange) -> Void]() + + @discardableResult + func observeChanges(using closure: @escaping (Storage, StoreChange) -> Void) -> ObservationToken { + let id = observations.insert(closure) + + return ObservationToken { [weak self] in + self?.observations.removeValue(forKey: id) + } + } + /// Initialize storage with configuration options. /// /// - Parameters: @@ -56,14 +74,26 @@ extension Storage: StorageAware { public func setObject(_ object: T, forKey key: String, expiry: Expiry? = nil) throws { try self.syncStorage.setObject(object, forKey: key, expiry: expiry) + notifyObservers(of: .addition) } public func removeAll() throws { try self.syncStorage.removeAll() + notifyObservers(of: .allDeletion) } public func removeExpiredObjects() throws { try self.syncStorage.removeExpiredObjects() + notifyObservers(of: .expiredDeletion) + } + + private func notifyObservers(of change: StoreChange) { + observations.values.forEach { [weak self] closure in + guard let strongSelf = self else { + return + } + closure(strongSelf, change) + } } }