@StateObject:什么是它,创建和管理 ObservableObject

作者: IT Sectr 发布日期: 2026-06-26 阅读时间: 9 分钟

@StateObject 是 SwiftUI 中的一个属性包装器,它在 View 的整个生命周期内创建并拥有一个 ObservableObject 实例。当 View 首次出现在屏幕上时,@StateObject 会初始化该对象并一直持有它,直到 View 从内存中移除。这保证了数据不会在界面重建时重置——例如,在切换主题或更新父 View 时。根据 Apple Developer Documentation (2025),@StateObject 应该用作 SwiftUI 层次结构中 ObservableObject 的主要真相来源(source of truth),而子 View 通过 @ObservedObject 或 @EnvironmentObject 接收已创建的对象。

要点

  • @StateObject — 用于在 View 内部创建和拥有 ObservableObject 的属性包装器。
  • 单次创建 — 对象在 View 生命周期内仅初始化一次,不会在重建时重新创建。
  • 真相来源 — 与 @ObservedObject 不同,@StateObject 是层次结构中的真相来源。
  • 生命周期 — 只要 View 存在于内存中,对象就存在,并随 View 一同销毁。
  • 初始化 — @StateObject 在创建时需要初始值,通常通过带参数的 init 实现。

SwiftUI 中的 @StateObject 是什么

@StateObject 是在 iOS 14 中引入的一个属性包装器,它允许 View 创建并拥有一个符合 ObservableObject 协议的类实例。与处理值类型(结构体)的 @State 不同,@StateObject 专为引用类型设计——那些可以通知 SwiftUI 其属性发生变化的类。

当 View 使用 @StateObject var viewModel: MyViewModel 时,SwiftUI 会在 View 首次显示时自动创建 MyViewModel 实例,并将其存储在框架的特殊存储中。在每次 View 更新时(例如,当父状态改变时),SwiftUI 不会重新创建对象——它会使用现有实例,直到 View 从层次结构中移除。

根据 Apple WWDC Session 10137 (2024),@StateObject 解决了 iOS 13 中存在 View 重建时数据丢失的问题,当时开发人员不得不在父 View 中创建 ObservableObject 并通过初始化器传递。这导致了代码重复和意外重新创建对象的风险。

swift
import SwiftUI

class CounterViewModel: ObservableObject {
    @Published var count: Int = 0
    
    func increment() {
        count += 1
    }
}

struct CounterView: View {
    @StateObject var viewModel = CounterViewModel()
    
    var body: some View {
        VStack {
            Text("计数: \(viewModel.count)")
            Button("增加", action: viewModel.increment)
        }
    }
}

@StateObject 如何工作

@StateObject 的机制基于 SwiftUI 与 Combine 框架的集成。当 ObservableObject 用 @Published 属性标记其属性时,SwiftUI 会自动通过 ObservableObject 协议中内置的发布者订阅更改。当已发布的属性发生变化时,对象通过 objectWillChange 发布者发送信号,这会触发观察该对象的所有 View 重新绘制。

SwiftUI 将 ObservableObject 实例存储在绑定到特定 View 实例的特殊存储中。该存储仅在首次渲染时创建一次,并一直存在直到 View 被销毁。这正是 @StateObject 保证对象引用稳定性的原因——SwiftUI 自动管理内存,不依赖于 View 的初始化器。

根据 objc.io — Thinking in SwiftUI (2025),@StateObject 的内部实现使用了与 @State 类似的机制,但针对引用类型:SwiftUI 在对象周围创建一个装箱包装器,并通过自己的分配器管理其生命周期,该分配器针对 View 层次结构的频繁重建进行了优化。

@StateObject 的生命周期

  • 创建 — 当 View 首次出现在屏幕上时,SwiftUI 调用对象的初始化器并保存引用。
  • 重建 — 在父 View 更新时,对象不会被重新创建,使用现有实例。
  • 销毁 — 当 View 离开屏幕并从层次结构中移除时,SwiftUI 调用对象的 deinit。

@StateObject 与 @ObservedObject:主要区别

@StateObject@ObservedObject 之间的主要区别在于谁拥有该对象。@StateObject 创建并存储对象——它是所有者。@ObservedObject 仅观察一个在别处创建并通过初始化器或属性传递的对象。

特性@StateObject@ObservedObject
所有权创建并拥有对象仅观察
初始化在 View 内部通过 init/default从外部,通过参数传递
生命周期绑定到 View 的生命周期不受 View 控制
重新创建更新时不会重新创建可以从外部替换
iOS 版本iOS 14+iOS 13+

规则很简单:如果 View 创建 ObservableObject —— 使用 @StateObject。如果 View 仅从父级接收现成的对象 —— 使用 @ObservedObject。违反此规则要么导致数据丢失(如果使用 @ObservedObject 进行拥有),要么导致对象过多创建(如果使用 @StateObject 进行观察)。

何时使用 @StateObject

@StateObject 应在作为特定数据集真相来源的 View 中使用。典型场景包括拥有自己 view model 的屏幕、导航栈的根屏幕以及管理自己状态的模态呈现。

  • 带有 view model 的屏幕 — 每个管理自己数据和逻辑的屏幕都应通过 @StateObject 创建其 view model。
  • 根 View — 在 NavigationStack 或 TabView 层次结构中,根元素创建数据,子元素通过 @ObservedObject 接收它们。
  • 模态窗口 — .sheet 和 .fullScreenCover 通常需要自己的 @StateObject 来管理表单或流程。
  • 可编辑列表 — 包含编辑表单的列表中的每一行都应拥有自己的 @StateObject。
swift
struct ProfileView: View {
    @StateObject var viewModel = ProfileViewModel()
    
    var body: some View {
        NavigationStack {
            Form {
                TextField("Name", text: $viewModel.name)
                TextField("Email", text: $viewModel.email)
                Button("保存") {
                    viewModel.saveProfile()
                }
            }
            .navigationTitle("Profile")
        }
    }
}

使用参数初始化 @StateObject

使用参数初始化 @StateObject 需要特殊的语法,因为 SwiftUI 独立管理对象的创建。不能简单地将参数传递给初始化器——需要使用逃逸闭包或单独的创建方法。

根据 Swift by Sundell (2024),最清晰的方法是使用工厂方法或闭包,SwiftUI 将在首次创建对象时调用。另一种方法——在父 View 中初始化 ObservableObject,并通过 @StateObject 使用标准初始化器传递它。

swift
class UserViewModel: ObservableObject {
    @Published var user: User
    
    init(user: User) {
        self.user = user
    }
}

struct UserDetailView: View {
    @StateObject var viewModel: UserViewModel
    
    init(user: User) {
        _viewModel = StateObject(wrappedValue: UserViewModel(user: user))
    }
    
    var body: some View {
        Text(viewModel.user.name)
    }
}

重要的是要记住,带有 @StateObject 的 View 初始化器必须在属性名称前使用下划线(_viewModel)来访问属性包装器本身,而不是其值。这是在初始化器中使用属性包装器的标准 Swift 模式。

@StateObject 的常见错误

最常见的错误是在本应拥有对象的 View 中使用 @ObservedObject 而不是 @StateObject。在这种情况下,每次父级重建时,对象都会重新创建,导致所有已积累的数据丢失。这个错误在使用 NavigationStack 或 TabView 的复杂层次结构中尤其隐蔽。

  • 导航时数据丢失 — 如果子屏幕为其自己的 view model 使用 @ObservedObject,则在返回和重新打开时数据将被重置。
  • 内存泄漏 — 在永远不会被移除的父 View 中创建 @StateObject 可能导致对象累积,如果每个子屏幕也在没有控制的情况下创建 @StateObject。
  • 对象重复 — 将一个 ObservableObject 传递给不同 View 中的多个 @StateObject 会创建多个彼此不同步的独立实例。

为避免这些问题,请遵循简单的规则:每个真相来源对应一个 @StateObject。如果数据需要在多个屏幕之间共享——在根 View 中创建一次 @StateObject,并通过 @ObservedObject 或 @EnvironmentObject 传递给子元素。

swift
// ❌ 错误: 使用 @ObservedObject 拥有对象
struct BadView: View {
    @ObservedObject var vm = ViewModel() // 每次更新都会重新创建!
}

// ✅ 正确: 使用 @StateObject 拥有
struct GoodView: View {
    @StateObject var vm = ViewModel() // 为 View 生命周期创建一次
}

常见问题

@StateObject 和 @State 有什么区别?

@State 处理值类型(结构体、字符串、数字)并将值直接存储在 SwiftUI 存储中。@StateObject 处理引用类型——符合 ObservableObject 的类。@State 适用于简单的局部状态,@StateObject 适用于具有逻辑和已发布属性的复杂对象。

可以在 iOS 13 中使用 @StateObject 吗?

不可以,@StateObject 仅从 iOS 14 及以上版本可用。对于 iOS 13,使用 @ObservedObject 并通过 @State 在父 View 中创建 ObservableObject,手动管理生命周期。另一种选择——对于不需要引用语义的数据,使用 @State 配合 struct 而不是 class。

如果在子 View 中使用 @StateObject,而该对象是从父级传递来的,会发生什么?

子 View 将创建自己的 ObservableObject 副本,完全独立于父级。一个副本中的更改不会反映在另一个副本中。这几乎总是一个错误:使用 @ObservedObject 从父级接收对象,仅使用 @StateObject 在 View 内部创建新对象。

通过 @StateObject 创建的对象何时被销毁?

当创建该对象的 View 完全从 SwiftUI 层次结构中移除时,对象被销毁。对于 NavigationStack 中的屏幕,这在从导航栈弹出时发生。对于模态窗口——在其关闭时。对于 TabView——在切换标签时,如果 View 未被缓存。

如何在初始化时向 @StateObject 传递参数?

使用自定义的 init,通过下划线访问属性包装器:_viewModel = StateObject(wrappedValue: MyViewModel(param: value))。此模式允许将任何参数传递给 ObservableObject,同时保证对象在 View 生命周期内仅创建一次。

总结

  • @StateObject — 用于在 View 内部创建和拥有 ObservableObject 的属性包装器,从 iOS 14 开始可用。
  • 单次创建保证 — 对象初始化一次,在 View 重建时不会重新创建。
  • 真相来源 — @StateObject 是真相来源,@ObservedObject 只是观察者。
  • 生命周期 — 只要 View 存在于 SwiftUI 层次结构中,对象就存在,并在离开时销毁。
  • 带参数的初始化 — 需要通过 _viewModel 和 StateObject(wrappedValue:) 访问属性包装器。
  • 所有权错误 — 使用 @ObservedObject 创建对象会导致重建时数据丢失。
  • 一个对象 — 一个 @StateObject — 对于共享数据,在根 View 中创建 @StateObject,并通过 @ObservedObject 传递给子元素。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读