Skip to content

Commit 9dd704c

Browse files
authored
Fix the issue where async atoms access their scope after the scope is disposed (#174)
1 parent 51565bd commit 9dd704c

21 files changed

+212
-352
lines changed

Sources/Atoms/AtomRoot.swift

+14-20
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,14 @@ public struct AtomRoot<Content: View>: View {
9292
public var body: some View {
9393
switch storage {
9494
case .managed:
95-
Managed(
95+
WithManagedStore(
9696
observers: observers,
9797
overrideContainer: overrideContainer,
9898
content: content
9999
)
100100

101101
case .unmanaged(let store):
102-
Scope(
102+
WithStore(
103103
store: store,
104104
observers: observers,
105105
overrideContainer: overrideContainer,
@@ -155,18 +155,16 @@ private extension AtomRoot {
155155
case unmanaged(store: AtomStore)
156156
}
157157

158-
struct Managed: View {
158+
struct WithManagedStore: View {
159159
let observers: [Observer]
160160
let overrideContainer: OverrideContainer
161161
let content: Content
162162

163163
@State
164164
private var store = AtomStore()
165-
@State
166-
private var state = ScopeState()
167165

168166
var body: some View {
169-
Scope(
167+
WithStore(
170168
store: store,
171169
observers: observers,
172170
overrideContainer: overrideContainer,
@@ -175,29 +173,25 @@ private extension AtomRoot {
175173
}
176174
}
177175

178-
struct Scope: View {
176+
struct WithStore: View {
179177
let store: AtomStore
180178
let observers: [Observer]
181179
let overrideContainer: OverrideContainer
182180
let content: Content
183181

184182
@State
185-
private var state = ScopeState()
183+
private var scopeToken = ScopeKey.Token()
186184

187185
var body: some View {
188-
let scopeKey = state.token.key
189-
let store = StoreContext.registerRoot(
190-
in: store,
191-
scopeKey: scopeKey,
192-
observers: observers,
193-
overrideContainer: overrideContainer
186+
content.environment(
187+
\.store,
188+
.root(
189+
store: store,
190+
scopeKey: scopeToken.key,
191+
observers: observers,
192+
overrideContainer: overrideContainer
193+
)
194194
)
195-
196-
state.unregister = {
197-
store.unregister(scopeKey: scopeKey)
198-
}
199-
200-
return content.environment(\.store, store)
201195
}
202196
}
203197
}

Sources/Atoms/AtomScope.swift

+36-22
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ public struct AtomScope<Content: View>: View {
5959
/// - id: An identifier represents this scope used for matching with scoped atoms.
6060
/// - content: The descendant view content that provides scoped context for atoms.
6161
public init<ID: Hashable>(id: ID = DefaultScopeID(), @ViewBuilder content: () -> Content) {
62-
let id = ScopeID(id)
63-
self.inheritance = .environment(id: id)
62+
let scopeID = ScopeID(id)
63+
self.inheritance = .environment(scopeID: scopeID)
6464
self.content = content()
6565
}
6666

@@ -82,9 +82,9 @@ public struct AtomScope<Content: View>: View {
8282
/// The content and behavior of the view.
8383
public var body: some View {
8484
switch inheritance {
85-
case .environment(let id):
85+
case .environment(let scopeID):
8686
WithEnvironment(
87-
id: id,
87+
scopeID: scopeID,
8888
observers: observers,
8989
overrideContainer: overrideContainer,
9090
content: content
@@ -110,7 +110,13 @@ public struct AtomScope<Content: View>: View {
110110
///
111111
/// - Returns: The self instance.
112112
public func scopedObserve(_ onUpdate: @MainActor @escaping (Snapshot) -> Void) -> Self {
113-
mutating(self) { $0.observers.append(Observer(onUpdate: onUpdate)) }
113+
if case .context = inheritance {
114+
assertionFailure(
115+
"[Atoms] AtomScope now ignores the given scoped observers if it's inheriting an ancestor scope. This will be deprecated soon."
116+
)
117+
return self
118+
}
119+
return mutating(self) { $0.observers.append(Observer(onUpdate: onUpdate)) }
114120
}
115121

116122
/// Override the atoms used in this scope with the given value.
@@ -128,7 +134,13 @@ public struct AtomScope<Content: View>: View {
128134
///
129135
/// - Returns: The self instance.
130136
public func scopedOverride<Node: Atom>(_ atom: Node, with value: @MainActor @escaping (Node) -> Node.Produced) -> Self {
131-
mutating(self) { $0.overrideContainer.addOverride(for: atom, with: value) }
137+
if case .context = inheritance {
138+
assertionFailure(
139+
"[Atoms] AtomScope now ignores the given scoped overrides if it's inheriting an ancestor scope. This will be deprecated soon."
140+
)
141+
return self
142+
}
143+
return mutating(self) { $0.overrideContainer.addOverride(for: atom, with: value) }
132144
}
133145

134146
/// Override the atoms used in this scope with the given value.
@@ -148,41 +160,43 @@ public struct AtomScope<Content: View>: View {
148160
///
149161
/// - Returns: The self instance.
150162
public func scopedOverride<Node: Atom>(_ atomType: Node.Type, with value: @MainActor @escaping (Node) -> Node.Produced) -> Self {
151-
mutating(self) { $0.overrideContainer.addOverride(for: atomType, with: value) }
163+
if case .context = inheritance {
164+
assertionFailure(
165+
"[Atoms] AtomScope now ignores the given scoped overrides if it's inheriting an ancestor scope. This will be deprecated soon."
166+
)
167+
return self
168+
}
169+
return mutating(self) { $0.overrideContainer.addOverride(for: atomType, with: value) }
152170
}
153171
}
154172

155173
private extension AtomScope {
156174
enum Inheritance {
157-
case environment(id: ScopeID)
175+
case environment(scopeID: ScopeID)
158176
case context(store: StoreContext)
159177
}
160178

161179
struct WithEnvironment: View {
162-
let id: ScopeID
180+
let scopeID: ScopeID
163181
let observers: [Observer]
164182
let overrideContainer: OverrideContainer
165183
let content: Content
166184

167185
@State
168-
private var state = ScopeState()
186+
private var scopeToken = ScopeKey.Token()
169187
@Environment(\.store)
170188
private var environmentStore
171189

172190
var body: some View {
173-
let scopeKey = state.token.key
174-
let store = environmentStore?.registerScope(
175-
scopeID: id,
176-
scopeKey: scopeKey,
177-
observers: observers,
178-
overrideContainer: overrideContainer
191+
content.environment(
192+
\.store,
193+
environmentStore?.scoped(
194+
scopeID: scopeID,
195+
scopeKey: scopeToken.key,
196+
observers: observers,
197+
overrideContainer: overrideContainer
198+
)
179199
)
180-
181-
state.unregister = {
182-
store?.unregister(scopeKey: scopeKey)
183-
}
184-
185-
return content.environment(\.store, store)
186200
}
187201
}
188202

Sources/Atoms/Context/AtomTestContext.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -437,8 +437,8 @@ internal extension AtomTestContext {
437437

438438
@usableFromInline
439439
var _store: StoreContext {
440-
.registerRoot(
441-
in: _state.store,
440+
.root(
441+
store: _state.store,
442442
scopeKey: _state.token.key,
443443
observers: [],
444444
overrideContainer: _state.overrideContainer

Sources/Atoms/Core/AtomCache.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@ internal protocol AtomCacheProtocol {
33

44
var atom: Node { get }
55
var value: Node.Produced { get }
6-
var initScopeKey: ScopeKey? { get }
6+
var initializedScope: Scope? { get }
77

88
func updated(value: Node.Produced) -> Self
99
}
1010

1111
internal struct AtomCache<Node: Atom>: AtomCacheProtocol, CustomStringConvertible {
1212
let atom: Node
1313
let value: Node.Produced
14-
let initScopeKey: ScopeKey?
14+
let initializedScope: Scope?
1515

1616
var description: String {
1717
"\(value)"
1818
}
1919

2020
func updated(value: Node.Produced) -> Self {
21-
AtomCache(atom: atom, value: value, initScopeKey: initScopeKey)
21+
AtomCache(atom: atom, value: value, initializedScope: initializedScope)
2222
}
2323
}

Sources/Atoms/Core/Scope.swift

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
internal struct Scope {
2+
let key: ScopeKey
23
let observers: [Observer]
34
let overrideContainer: OverrideContainer
45
let ancestorScopeKeys: [ScopeID: ScopeKey]

Sources/Atoms/Core/ScopeState.swift

-33
This file was deleted.

0 commit comments

Comments
 (0)