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

NavigationStack中StateObject/View Model无法即时释放的问题求助

解决SwiftUI NavigationStack中视图消失后Model未及时释放的问题

问题复现

以下代码中,使用@StateObject绑定的MyModel在通过NavigationLink跳转后,已消失视图的Model不会立即释放,仅在返回导航栈时才会被销毁:

import SwiftUI

@MainActor
class MyModel: ObservableObject {
    let letvalue: Int
    @Published var value: Int

    init(_ value: Int) {
        print("init model \(value)")
        self.value = value
        self.letvalue = value
    }

    deinit {
        print("deinit model \(letvalue)")
    }
}


struct MyView: View {
    @StateObject var model: MyModel

    init(_ value: Int) {
        self._model = StateObject(wrappedValue: MyModel(value))
    }

    var body: some View {
        VStack {
            Text("Hello \(model.value)")
            NavigationLink(destination: {
                MyView(model.value + 1)
            }, label: {
                Text("goto \(model.value + 1)")
            })
        }
        .onAppear { [weak model] in
            print("onAppear \(model?.value)")
        }
        .onDisappear { [weak model] in
            print("onDisappear \(model?.value)")
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationStack {
            MyView(0)
                .padding()
        }
    }
}

运行日志显示,跳转后仅触发onDisappear,但Model的deinit未执行:

init model 0
deinit model 0
init model 0
onAppear Optional(0)
init model 1
onAppear Optional(1)
onDisappear Optional(0)
init model 2
onAppear Optional(2)
onDisappear Optional(1)
init model 3
onAppear Optional(3)
onDisappear Optional(2)

问题原因

SwiftUI的NavigationStack默认缓存栈内已消失的视图实例,用于优化返回时的加载速度,但这会导致绑定的ObservableObject(包括@StateObject/@ObservedObject)无法及时释放,对于包含AVPlayer等资源密集型对象的Model,会造成持续的资源占用。

解决方案

方案1:使用navigationDestination配合编程式导航

通过navigationDestination结合NavigationLink的item机制,让SwiftUI仅在需要时创建视图实例,跳转后前一个视图的Model会立即被释放:

import SwiftUI

@MainActor
class MyModel: ObservableObject {
    let letvalue: Int
    @Published var value: Int

    init(_ value: Int) {
        print("init model \(value)")
        self.value = value
        self.letvalue = value
    }

    deinit {
        print("deinit model \(letvalue)")
    }
}

struct MyView: View {
    let value: Int
    @StateObject private var model: MyModel
    @State private var nextValue: Int?
    @Environment(\.dismiss) private var dismiss

    init(_ value: Int) {
        self.value = value
        self._model = StateObject(wrappedValue: MyModel(value))
    }

    var body: some View {
        VStack {
            Text("Hello \(model.value)")
            Button("goto \(model.value + 1)") {
                nextValue = model.value + 1
            }
            Button("返回") {
                dismiss()
            }
        }
        .navigationDestination(item: $nextValue) { val in
            MyView(val)
        }
        .onAppear {
            print("onAppear \(model.value)")
        }
        .onDisappear {
            print("onDisappear \(model.value)")
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationStack {
            MyView(0)
                .padding()
        }
    }
}

修改后的日志会显示Model在跳转后立即释放:

init model 0
onAppear 0
init model 1
onAppear 1
onDisappear 0
deinit model 0
init model 2
onAppear 2
onDisappear 1
deinit model 1

方案2:手动在onDisappear中清理资源

如果无法修改导航逻辑,可在视图消失时手动清理Model中的资源(如AVPlayer暂停、置空),即使Model未被释放,也能避免资源浪费:

import SwiftUI
import AVFoundation

@MainActor
class MyModel: ObservableObject {
    let letvalue: Int
    @Published var value: Int
    var player: AVPlayer? // 示例资源密集型对象

    init(_ value: Int) {
        print("init model \(value)")
        self.value = value
        self.letvalue = value
        // 初始化资源
        if let url = URL(string: "https://example.com/video.mp4") {
            player = AVPlayer(url: url)
        }
    }

    // 资源清理方法
    func cleanUpResources() {
        player?.pause()
        player = nil
        print("resources cleaned for model \(letvalue)")
    }

    deinit {
        print("deinit model \(letvalue)")
    }
}

struct MyView: View {
    @StateObject var model: MyModel

    init(_ value: Int) {
        self._model = StateObject(wrappedValue: MyModel(value))
    }

    var body: some View {
        VStack {
            Text("Hello \(model.value)")
            NavigationLink(destination: {
                MyView(model.value + 1)
            }, label: {
                Text("goto \(model.value + 1)")
            })
        }
        .onAppear { [weak model] in
            print("onAppear \(model?.value)")
        }
        .onDisappear { [weak model] in
            print("onDisappear \(model?.value)")
            model?.cleanUpResources() // 触发资源清理
        }
    }
}

方案3:自定义导航管理器控制Model生命周期

通过自定义导航栈管理器,手动管理每个页面的Model实例,当页面被移除时主动释放Model:

import SwiftUI

@MainActor
class NavigationManager: ObservableObject {
    @Published var path: [Int] = []
    private var modelStore: [Int: MyModel] = [:]

    // 推入新页面并创建Model
    func push(to value: Int) {
        let model = MyModel(value)
        modelStore[value] = model
        path.append(value)
    }

    // 弹出页面并释放对应Model
    func pop() {
        guard let lastValue = path.last else { return }
        path.removeLast()
        modelStore[lastValue] = nil
    }
}

@MainActor
class MyModel: ObservableObject {
    let letvalue: Int
    @Published var value: Int

    init(_ value: Int) {
        print("init model \(value)")
        self.value = value
        self.letvalue = value
    }

    deinit {
        print("deinit model \(letvalue)")
    }
}

struct MyView: View {
    let value: Int
    @ObservedObject var model: MyModel
    @EnvironmentObject var navManager: NavigationManager

    var body: some View {
        VStack {
            Text("Hello \(model.value)")
            Button("goto \(model.value + 1)") {
                navManager.push(to: model.value + 1)
            }
            Button("返回") {
                navManager.pop()
            }
        }
        .onAppear {
            print("onAppear \(model.value)")
        }
        .onDisappear {
            print("onDisappear \(model.value)")
        }
    }
}

struct ContentView: View {
    @StateObject private var navManager = NavigationManager()

    var body: some View {
        NavigationStack(path: $navManager.path) {
            MyView(value: 0, model: MyModel(0))
                .padding()
                .navigationDestination(for: Int.self) { value in
                    if let model = navManager.modelStore[value] {
                        MyView(value: value, model: model)
                    }
                }
        }
        .environmentObject(navManager)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 22:02:31