ios – Why are objects still in memory after emptying NavigationStack path?

0
235


I’m trying to implement a Coordinator for managing a flow. The state is stored inside the CoordinatorStore. There are 2 @Published properties for managing the flow. The screen property controls which View is currently shown and path controls the navigation stack of the stack view. Details of the implementation can be found below.

With the current implementation and after the following actions: showA -> showB -> showInitial

I would expect that StoreA and StoreB would be deallocated from memory since path, which holds StoreA and StoreB via enum associated values, gets emptied.

But that doesn’t happen, and if I repeat the actions again there would be 2 StoreA and 2 StoreB in memory and so on. Am I missing something?

I will also attach a screenshot of the memory debugger snapshot after doing the initial set of actions.

enum Path: Hashable {
    case a(StoreA)
    case b(StoreB)
}

enum Screen {
    case initial
    case stack
}

final class CoordinatorStore: ObservableObject {
    @Published var path: [Path] = []
    @Published var screen: Screen = .stack
    
    func showA() {
        let store = StoreA()
        path.append(.a(store))
    }
    
    func showB() {
        let store = StoreB()
        path.append(.b(store))
    }
    
    func showInitial() {
        path = []
        screen = .initial
    }
    
    func showStack() {
        screen = .stack
    }
}
struct Coordinator: View {
    @ObservedObject var store: CoordinatorStore
    
    var body: some View {
        switch store.screen {
        case .initial: initial
        case .stack: stack
        }
    }
    
    var stack: some View {
        NavigationStack(path: $store.path) {
            VStack {
                Text("Root")
            }
            .toolbar {
                Button(action: self.store.showA) {
                    Text("Push A")
                }
            }
            .navigationDestination(for: Path.self) { path in
                switch path {
                case .a(let store):
                    ViewA(store: store)
                        .toolbar {
                            Button(action: self.store.showB) {
                                Text("Push B")
                            }
                        }
                case .b(let store):
                    ViewB(store: store)
                        .toolbar {
                            Button(action: self.store.showInitial) {
                                Text("Show Initial")
                            }
                        }
                }
            }
        }
    }
    
    var initial: some View {
        VStack {
            Text("Initial")
            Button(action: store.showStack) {
                Text("Go to Stack")
            }
        }
    }
}
struct ViewA: View {
    @ObservedObject var store: StoreA
    
    var body: some View {
        Text("View A")
    }
}

final class StoreA: NSObject, ObservableObject {
    deinit {
        print("Deinit: \(String(describing: self))")
    }
}
struct ViewB: View {
    @ObservedObject var store: StoreB
    
    var body: some View {
        Text("View B")
    }
}

final class StoreB: NSObject, ObservableObject {
    deinit {
        print("Deinit: \(String(describing: self))")
    }
}

Memory Debugger Snapshot deb