From 8f9ad7fc225d768b47473d0dea67f12b883bce56 Mon Sep 17 00:00:00 2001 From: ra1028 Date: Wed, 17 Apr 2024 21:08:40 +0900 Subject: [PATCH 1/2] Explicitly define scoped observe feature --- .../ExampleTimeTravel/ExampleTimeTravel.swift | 2 +- README.md | 6 +++--- Sources/Atoms/AtomRoot.swift | 7 ++++--- Sources/Atoms/AtomScope.swift | 17 ++++++++--------- Sources/Atoms/Context/AtomTestContext.swift | 4 ++-- Sources/Atoms/Core/Environment.swift | 1 + Sources/Atoms/Core/StoreContext.swift | 15 ++++++++++----- Sources/Atoms/Deprecated.swift | 5 +++++ Tests/AtomsTests/Core/StoreContextTests.swift | 1 + Tests/AtomsTests/Utilities/Util.swift | 1 + 10 files changed, 36 insertions(+), 23 deletions(-) diff --git a/Examples/Packages/iOS/Sources/ExampleTimeTravel/ExampleTimeTravel.swift b/Examples/Packages/iOS/Sources/ExampleTimeTravel/ExampleTimeTravel.swift index ec745b7a..86101e3b 100644 --- a/Examples/Packages/iOS/Sources/ExampleTimeTravel/ExampleTimeTravel.swift +++ b/Examples/Packages/iOS/Sources/ExampleTimeTravel/ExampleTimeTravel.swift @@ -89,7 +89,7 @@ struct TimeTravelDebug: View { } .padding() } - .observe { snapshot in + .scopedObserve { snapshot in Task { snapshots = Array(snapshots.prefix(position + 1)) snapshots.append(snapshot) diff --git a/README.md b/README.md index 908c6434..f18d4f01 100644 --- a/README.md +++ b/README.md @@ -1214,7 +1214,7 @@ See the [Override Atoms](#override-atoms) and [Debugging](#debugging) sections f AtomScope { CounterView() } -.observe { snapshot in +.scopedObserve { snapshot in if let count = snapshot.lookup(CounterAtom()) { print(count) } @@ -1443,8 +1443,8 @@ var debugButton: some View { } ``` -Or, you can observe all updates of atoms and always continue to receive `Snapshots` at that point in time through `observe(_:)` modifier of [AtomRoot](#atomroot) or [AtomScope](#atomscope). -Note that observing in `AtomRoot` will receive all atom updates that happened in the whole app, but observing in `AtomScope` will only receive atoms used in the descendant views. +Or, you can observe all state changes and always continue to receive `Snapshots` at that point in time with `observe(_:)` modifier of [AtomRoot](#atomroot) or with `scopedObserve(_:)` modifier of [AtomScope](#atomscope). +Note that observing in `AtomRoot` will receive every state changes that happened in the whole app, but observing in `AtomScope` will observe changes of atoms used in the scope. ```swift AtomRoot { diff --git a/Sources/Atoms/AtomRoot.swift b/Sources/Atoms/AtomRoot.swift index 51d4d2d8..43c08793 100644 --- a/Sources/Atoms/AtomRoot.swift +++ b/Sources/Atoms/AtomRoot.swift @@ -108,9 +108,8 @@ public struct AtomRoot: View { } } - /// For debugging purposes, each time there is a change in the internal state, - /// a snapshot is taken that captures the state of the atoms and their dependency graph - /// at that point in time. + /// Observes the state changes along with a snapshot capturing the whole atom states and their dependency + /// graph at the point in time for debugging purpose. /// /// - Parameter onUpdate: A closure to handle a snapshot of recent updates. /// @@ -178,6 +177,7 @@ private extension AtomRoot { scopeKey: ScopeKey(token: state.token), inheritedScopeKeys: [:], observers: observers, + scopedObservers: [], overrides: overrides ) ) @@ -206,6 +206,7 @@ private extension AtomRoot { scopeKey: ScopeKey(token: state.token), inheritedScopeKeys: [:], observers: observers, + scopedObservers: [], overrides: overrides ) ) diff --git a/Sources/Atoms/AtomScope.swift b/Sources/Atoms/AtomScope.swift index 094fa2d8..f28dde19 100644 --- a/Sources/Atoms/AtomScope.swift +++ b/Sources/Atoms/AtomScope.swift @@ -2,13 +2,13 @@ import SwiftUI /// A view to override or monitor atoms in scope. /// -/// This view allows you to monitor changes of atoms used in descendant views by``AtomScope/observe(_:)``. +/// This view allows you to monitor changes of atoms used in descendant views by``AtomScope/scopedObserve(_:)``. /// /// ```swift /// AtomScope { /// CounterView() /// } -/// .observe { snapshot in +/// .scopedObserve { snapshot in /// if let count = snapshot.lookup(CounterAtom()) { /// print(count) /// } @@ -87,17 +87,16 @@ public struct AtomScope: View { } } - /// For debugging purposes, each time there is a change in the internal state, - /// a snapshot is taken that captures the state of the atoms and their dependency graph - /// at that point in time. + /// Observes the state changes along with a snapshot capturing the whole atom states and their dependency + /// graph at the point in time for debugging purposes. /// - /// Note that unlike observed by ``AtomRoot``, this is triggered only by internal state changes - /// caused by atoms use in this scope. + /// Note that unlike ``AtomRoot/observe(_:)``, this observes only the state changes caused by atoms + /// used in this scope. /// /// - Parameter onUpdate: A closure to handle a snapshot of recent updates. /// /// - Returns: The self instance. - public func observe(_ onUpdate: @escaping @MainActor (Snapshot) -> Void) -> Self { + public func scopedObserve(_ onUpdate: @escaping @MainActor (Snapshot) -> Void) -> Self { mutating(self) { $0.observers.append(Observer(onUpdate: onUpdate)) } } @@ -181,7 +180,7 @@ private extension AtomScope { content.environment( \.store, context._store.inherited( - observers: observers, + scopedObservers: observers, overrides: overrides ) ) diff --git a/Sources/Atoms/Context/AtomTestContext.swift b/Sources/Atoms/Context/AtomTestContext.swift index 73fb3ee2..7a1f7a63 100644 --- a/Sources/Atoms/Context/AtomTestContext.swift +++ b/Sources/Atoms/Context/AtomTestContext.swift @@ -5,8 +5,7 @@ import Foundation /// /// This context has a store that manages the state of atoms, so it can be used to test individual /// atoms or their interactions with other atoms without depending on the SwiftUI view tree. -/// Furthermore, unlike other contexts, it is possible to override or observe changes in atoms -/// through this context. +/// Furthermore, unlike other contexts, it is possible to override atoms through this context. @MainActor public struct AtomTestContext: AtomWatchableContext { private let location: SourceLocation @@ -444,6 +443,7 @@ internal extension AtomTestContext { scopeKey: ScopeKey(token: _state.token), inheritedScopeKeys: [:], observers: [], + scopedObservers: [], overrides: _state.overrides ) } diff --git a/Sources/Atoms/Core/Environment.swift b/Sources/Atoms/Core/Environment.swift index a431f6bd..6af8da24 100644 --- a/Sources/Atoms/Core/Environment.swift +++ b/Sources/Atoms/Core/Environment.swift @@ -14,6 +14,7 @@ private struct StoreEnvironmentKey: EnvironmentKey { scopeKey: ScopeKey(token: ScopeKey.Token()), inheritedScopeKeys: [:], observers: [], + scopedObservers: [], overrides: [:], enablesAssertion: true ) diff --git a/Sources/Atoms/Core/StoreContext.swift b/Sources/Atoms/Core/StoreContext.swift index c9705535..1b04d9c6 100644 --- a/Sources/Atoms/Core/StoreContext.swift +++ b/Sources/Atoms/Core/StoreContext.swift @@ -7,6 +7,7 @@ internal struct StoreContext { private let scopeKey: ScopeKey private let inheritedScopeKeys: [ScopeID: ScopeKey] private let observers: [Observer] + private let scopedObservers: [Observer] private let overrides: [OverrideKey: any AtomOverrideProtocol] private let enablesAssertion: Bool @@ -15,6 +16,7 @@ internal struct StoreContext { scopeKey: ScopeKey, inheritedScopeKeys: [ScopeID: ScopeKey], observers: [Observer], + scopedObservers: [Observer], overrides: [OverrideKey: any AtomOverrideProtocol], enablesAssertion: Bool = false ) { @@ -22,19 +24,21 @@ internal struct StoreContext { self.scopeKey = scopeKey self.inheritedScopeKeys = inheritedScopeKeys self.observers = observers + self.scopedObservers = scopedObservers self.overrides = overrides self.enablesAssertion = enablesAssertion } func inherited( - observers: [Observer], + scopedObservers: [Observer], overrides: [OverrideKey: any AtomOverrideProtocol] ) -> StoreContext { StoreContext( weakStore, scopeKey: scopeKey, inheritedScopeKeys: inheritedScopeKeys, - observers: self.observers + observers, + observers: observers, + scopedObservers: self.scopedObservers + scopedObservers, overrides: self.overrides.merging(overrides) { $1 }, enablesAssertion: enablesAssertion ) @@ -50,7 +54,8 @@ internal struct StoreContext { weakStore, scopeKey: scopeKey, inheritedScopeKeys: mutating(inheritedScopeKeys) { $0[scopeID] = scopeKey }, - observers: self.observers + observers, + observers: self.observers, + scopedObservers: observers, overrides: overrides, enablesAssertion: enablesAssertion ) @@ -612,13 +617,13 @@ private extension StoreContext { } func notifyUpdateToObservers() { - guard !observers.isEmpty else { + guard !observers.isEmpty || !scopedObservers.isEmpty else { return } let snapshot = snapshot() - for observer in observers { + for observer in observers + scopedObservers { observer.onUpdate(snapshot) } } diff --git a/Sources/Atoms/Deprecated.swift b/Sources/Atoms/Deprecated.swift index 89b0826d..ca347fd2 100644 --- a/Sources/Atoms/Deprecated.swift +++ b/Sources/Atoms/Deprecated.swift @@ -27,4 +27,9 @@ public extension AtomScope { AtomRoot(storesIn: store, content: content) } } + + @available(*, deprecated, renamed: "scopedObserve(_:)") + func observe(_ onUpdate: @escaping @MainActor (Snapshot) -> Void) -> Self { + scopedObserve(onUpdate) + } } diff --git a/Tests/AtomsTests/Core/StoreContextTests.swift b/Tests/AtomsTests/Core/StoreContextTests.swift index 48974617..f6198859 100644 --- a/Tests/AtomsTests/Core/StoreContextTests.swift +++ b/Tests/AtomsTests/Core/StoreContextTests.swift @@ -16,6 +16,7 @@ final class StoreContextTests: XCTestCase { scopeKey: scopeKey, inheritedScopeKeys: [:], observers: [], + scopedObservers: [], overrides: [ OverrideKey(atom): AtomOverride> { _ in 10 diff --git a/Tests/AtomsTests/Utilities/Util.swift b/Tests/AtomsTests/Utilities/Util.swift index 93805169..825eea79 100644 --- a/Tests/AtomsTests/Utilities/Util.swift +++ b/Tests/AtomsTests/Utilities/Util.swift @@ -68,6 +68,7 @@ extension StoreContext { scopeKey: ScopeKey(token: ScopeKey.Token()), inheritedScopeKeys: [:], observers: observers, + scopedObservers: [], overrides: overrides ) } From da13a3c1d415b63b275ea83fde76e5500cec5381 Mon Sep 17 00:00:00 2001 From: ra1028 Date: Wed, 17 Apr 2024 21:16:12 +0900 Subject: [PATCH 2/2] Refactoring --- Sources/Atoms/AtomRoot.swift | 4 ++-- Sources/Atoms/AtomScope.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Atoms/AtomRoot.swift b/Sources/Atoms/AtomRoot.swift index 43c08793..7171ca26 100644 --- a/Sources/Atoms/AtomRoot.swift +++ b/Sources/Atoms/AtomRoot.swift @@ -108,8 +108,8 @@ public struct AtomRoot: View { } } - /// Observes the state changes along with a snapshot capturing the whole atom states and their dependency - /// graph at the point in time for debugging purpose. + /// Observes the state changes with a snapshot that captures the whole atom states and + /// their dependency graph at the point in time for debugging purposes. /// /// - Parameter onUpdate: A closure to handle a snapshot of recent updates. /// diff --git a/Sources/Atoms/AtomScope.swift b/Sources/Atoms/AtomScope.swift index f28dde19..397d2fe1 100644 --- a/Sources/Atoms/AtomScope.swift +++ b/Sources/Atoms/AtomScope.swift @@ -87,8 +87,8 @@ public struct AtomScope: View { } } - /// Observes the state changes along with a snapshot capturing the whole atom states and their dependency - /// graph at the point in time for debugging purposes. + /// Observes the state changes with a snapshot that captures the whole atom states and + /// their dependency graph at the point in time for debugging purposes. /// /// Note that unlike ``AtomRoot/observe(_:)``, this observes only the state changes caused by atoms /// used in this scope.