You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

清空NavigationStack path后对象仍驻留内存的原因排查

问题

我正在实现一个用于管理流程的Coordinator,状态存储在CoordinatorStore中,该类包含两个@Published属性:screen用于控制当前显示的View,path用于控制导航栈的内容。

执行操作流程:showA→showB→showInitial→Go to Stack后,我原本认为由于持有StoreA和StoreB实例的path已被清空,这两个对象会被内存释放,但实际并未发生。重复执行该操作流程后,内存中会累积多个StoreA和StoreB实例,请问问题出在哪里?


核心定义代码

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
    }
}

Coordinator视图代码

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")
            }
        }
    }
}

ViewA与StoreA代码

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))")
    }
}

ViewB与StoreB代码

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))")
    }
}

原因分析

问题出在NavigationStack的内部缓存机制:当切换到.initial视图时,NavigationStack会被从视图树中移除,但SwiftUI会对之前的导航栈相关视图(包括对应的Store实例)进行缓存,以便快速恢复。即使清空了path,这个内部缓存仍然持有StoreA和StoreB的引用,导致它们无法被释放。

另外,Path枚举直接持有Store实例,而NavigationStack在处理导航目标时会保留这些枚举值的引用,加上缓存机制,进一步延长了对象的生命周期。

解决方案

有两种可行的解决方式:

方式一:使用标识而非直接持有实例

修改Path枚举,让它只存储标识信息,在需要创建视图时再初始化对应的Store:

enum Path: Hashable {
    case a
    case b
}

// 修改Coordinator中的navigationDestination部分
.navigationDestination(for: Path.self) { path in
    switch path {
    case .a:
        ViewA(store: StoreA())
            .toolbar {
                Button(action: self.store.showB) {
                    Text("Push B")
                }
            }
    case .b:
        ViewB(store: StoreB())
            .toolbar {
                Button(action: self.store.showInitial) {
                    Text("Show Initial")
                }
            }
    }
}

这种方式下,Store实例的生命周期完全由对应的View管理,当View被销毁时,Store也会被释放。

方式二:手动清除NavigationStack的缓存

在切换到.initial视图时,通过给NavigationStack添加一个id来重置它的状态,让SwiftUI完全重建导航栈,从而清除之前的缓存:

// 在CoordinatorStore中添加
@Published var navigationStackId = UUID()

func showInitial() {
    path = []
    screen = .initial
    navigationStackId = UUID() // 重置id
}

// 在stack视图中使用
NavigationStack(path: $store.path) {
    // 原有内容不变
}
.id(store.navigationStackId)

这样每次返回初始视图再进入栈视图时,NavigationStack会被完全重建,之前的缓存会被清理,Store实例就能正常释放。


内容的提问来源于stack exchange,提问作者hydro1337x

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 06:45:36