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

SwiftUI(Mac)@Observable数组引发内存泄漏及App生命周期重复触发问题

SwiftUI @Observable 数组访问引发的重复初始化与生命周期异常问题

问题现象

使用带有数组属性的@Observable视图模型时,若在init方法中访问数组(例如判断isEmpty),修改数组会触发WindowGroup的body重新计算,导致视图模型被二次初始化,出现以下异常:

  • 首次运行打印Init,点击Update按钮后再次打印Init
  • 内存中存在两个ContentViewModel实例,deinit方法从未触发
  • 移除if items.isEmpty {}代码后问题完全消失,且该问题仅出现在@Observable中,ObservableObject+ObservedObject组合无此现象

复现代码

Root App

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

ViewModel

@Observable
class ContentViewModel {
    var items = [String]()
    
    init() {
        print("Init")
        if items.isEmpty {} // 触发问题的关键代码
    }

    deinit {
        print("deinit")
    }
    
    func update() {
        items.append("New")
    }
}

View

struct ContentView: View {
    @State var viewModel: ContentViewModel = .init()
    
    var body: some View {
        Button("Update", action: viewModel.update)
    }
}

原因分析

这是@Observable宏的属性观察机制导致的:
@Observable采用细粒度的属性访问追踪,在init方法中访问数组属性时,会提前将观察关联绑定到App场景的生命周期上下文。当数组被修改时,错误地触发了WindowGroup的视图刷新,进而导致@State包装的视图模型被重新初始化,旧实例因仍被观察关联引用而无法释放。

而ObservableObject基于@Published的发布订阅模型,仅在属性显式发布时触发视图更新,不会在init阶段建立错误的观察绑定,因此无此问题。

解决方案

方案1:延迟数组逻辑到视图初始化后执行

将原init中的数组访问逻辑移到视图的onAppear回调中执行,避免在@Observable的init中触发观察关联:
修改ViewModel:

@Observable
class ContentViewModel {
    var items = [String]()
    
    init() {
        print("Init")
    }

    deinit {
        print("deinit")
    }
    
    func setup() {
        if items.isEmpty {} // 原init中的逻辑移到此处
    }
    
    func update() {
        items.append("New")
    }
}

修改ContentView:

struct ContentView: View {
    @State var viewModel: ContentViewModel = .init()
    
    var body: some View {
        Button("Update", action: viewModel.update)
            .onAppear(perform: viewModel.setup)
    }
}

方案2:改用ObservableObject实现

如果业务允许,切换到ObservableObject+@Published的组合,规避@Observable的当前问题:

class ContentViewModel: ObservableObject {
    @Published var items = [String]()
    
    init() {
        print("Init")
        if items.isEmpty {}
    }

    deinit {
        print("deinit")
    }
    
    func update() {
        items.append("New")
    }
}

视图中使用@StateObject:

struct ContentView: View {
    @StateObject var viewModel: ContentViewModel = .init()
    
    var body: some View {
        Button("Update", action: viewModel.update)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 15:56:07