Skip to content

Commit d83d00c

Browse files
authored
Remove child store from parent when invalidated (#3729)
* Clear child store from parent when it is invalidated. * wip * wip * wip
1 parent b914abb commit d83d00c

File tree

1 file changed

+27
-5
lines changed

1 file changed

+27
-5
lines changed

Sources/ComposableArchitecture/Store.swift

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,14 @@ import SwiftUI
9898
#else
9999
@preconcurrency@MainActor
100100
#endif
101-
public final class Store<State, Action> {
101+
public final class Store<State, Action>: _Store {
102102
var children: [ScopeID<State, Action>: AnyObject] = [:]
103+
private weak var parent: (any _Store)?
104+
private let scopeID: AnyHashable?
105+
106+
func removeChild(scopeID: AnyHashable) {
107+
children[scopeID as! ScopeID<State, Action>] = nil
108+
}
103109

104110
let core: any Core<State, Action>
105111
@_spi(Internals) public var effectCancellables: [UUID: AnyCancellable] { core.effectCancellables }
@@ -139,6 +145,7 @@ public final class Store<State, Action> {
139145

140146
init() {
141147
self.core = InvalidCore()
148+
self.scopeID = nil
142149
}
143150

144151
deinit {
@@ -266,7 +273,7 @@ public final class Store<State, Action> {
266273
let id,
267274
let child = children[id] as? Store<ChildState, ChildAction>
268275
else {
269-
let child = Store<ChildState, ChildAction>(core: childCore())
276+
let child = Store<ChildState, ChildAction>(core: childCore(), scopeID: id, parent: self)
270277
if core.canStoreCacheChildren, let id {
271278
children[id] = child
272279
}
@@ -313,18 +320,24 @@ public final class Store<State, Action> {
313320
core.send(action)
314321
}
315322

316-
private init(core: some Core<State, Action>) {
323+
private init(core: some Core<State, Action>, scopeID: AnyHashable?, parent: (any _Store)?) {
317324
defer { Logger.shared.log("\(storeTypeName(of: self)).init") }
318325
self.core = core
326+
self.parent = parent
327+
self.scopeID = scopeID
319328

320329
if let stateType = State.self as? any ObservableState.Type {
321330
func subscribeToDidSet<T: ObservableState>(_ type: T.Type) -> AnyCancellable {
322331
return core.didSet
323-
.prefix { [weak self] _ in self?.core.isInvalid != true }
332+
.prefix { [weak self] _ in self?.core.isInvalid == false }
324333
.compactMap { [weak self] in (self?.currentState as? T)?._$id }
325334
.removeDuplicates()
326335
.dropFirst()
327336
.sink { [weak self] _ in
337+
guard let scopeID = self?.scopeID
338+
else { return }
339+
parent?.removeChild(scopeID: scopeID)
340+
} receiveValue: { [weak self] _ in
328341
guard let self else { return }
329342
self._$observationRegistrar.withMutation(of: self, keyPath: \.currentState) {}
330343
}
@@ -337,7 +350,11 @@ public final class Store<State, Action> {
337350
initialState: R.State,
338351
reducer: R
339352
) {
340-
self.init(core: RootCore(initialState: initialState, reducer: reducer))
353+
self.init(
354+
core: RootCore(initialState: initialState, reducer: reducer),
355+
scopeID: nil,
356+
parent: nil
357+
)
341358
}
342359

343360
/// A publisher that emits when state changes.
@@ -565,3 +582,8 @@ let _isStorePerceptionCheckingEnabled: Bool = {
565582
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
566583
extension Store: Observable {}
567584
#endif
585+
586+
@MainActor
587+
private protocol _Store: AnyObject {
588+
func removeChild(scopeID: AnyHashable)
589+
}

0 commit comments

Comments
 (0)