@StateObject:是什么、与@ObservedObject的区别及示例

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

@StateObject 是 SwiftUI 中的一个 Property Wrapper,用于直接在视图中创建和拥有 ObservableObject 实例。SwiftUI 保证该对象在视图的生命周期内只初始化一次,并且在重复渲染时不会重新创建。根据 Apple Developer Documentation (2025),@StateObject 建议用于创建数据源的根视图。@StateObject 是在 SwiftUI 层级中拥有 ObservableObject 的正确选择。

要点

  • @StateObject — 用于在视图中创建和拥有 ObservableObject 的 Property Wrapper
  • 唯一实例 — 对象只创建一次,在渲染时不会重新创建
  • 真相来源 — @StateObject 保证整个层级数据的稳定性
  • 与 @ObservedObject 的区别 — @ObservedObject 不拥有对象,可能会丢失它
  • 根视图 — @StateObject 用于创建对象的视图中

SwiftUI 中的 @StateObject 是什么?

@StateObject 是一个 Property Wrapper,出现在 SwiftUI 2.0(iOS 14)中,结合了 @ObservedObject 和 @State 的功能。与 @ObservedObject 一样,它订阅 ObservableObject 的变化。与 @State 一样,它保证数据能够承受视图结构的重复初始化。@StateObject 在视图首次出现在屏幕上时创建一次对象,并将其存储在 SwiftUI 堆中。

在 @StateObject 出现之前,开发人员对所有 ObservableObject(包括在视图中创建的)都使用 @ObservedObject。这在父视图更新时导致频繁的数据丢失,因为视图结构被重新创建,同时带走了 @ObservedObject 实例。@StateObject 通过增加稳定性保证解决了这个问题。

基本规则:@StateObject 应用于在默认初始化器(let model = ViewModel())中创建对象的视图。接收此对象的子视图使用 @ObservedObject。这种划分保证了整个层级中的唯一真相来源。

@StateObject 的生命周期

SwiftUI 通过存储管理器管理 @StateObject 的生命周期,类似于 @State。在视图首次出现时,SwiftUI 为对象分配内存并将其保存在持久区域中。在重复渲染(调用 body)时,对象不会重新创建——使用现有实例。只要视图在层级中,对象就存在。

当视图从层级中移除时,SwiftUI 销毁 @StateObject,调用 deinit。当视图重新添加到层级时,会创建一个新实例。在设计时需要考虑到这一点:如果需要在视图移除之间保留数据,请使用服务层(单例或 DI)或 @AppStorage 进行持久化。

swift
class TimerViewModel: ObservableObject {
    @Published var seconds: Int = 0
    private var timer: Timer?

    func start() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.seconds += 1
        }
    }

    deinit {
        timer?.invalidate()
    }
}

struct TimerView: View {
    @StateObject var viewModel = TimerViewModel()

    var body: some View {
        Text("\(viewModel.seconds)s")
            .onAppear { viewModel.start() }
    }
}

在示例中,TimerViewModel 通过 @StateObject 创建,只要 TimerView 在屏幕上就存在。Timer 在 onAppear 中启动,在 deinit 中停止。如果使用 @ObservedObject,每次 TimerView 渲染都会创建一个新的 TimerViewModel(秒 = 0),计时器永远无法正常工作。@StateObject 保证 viewModel 是唯一且稳定的。

@StateObject vs @ObservedObject:比较

@StateObject 和 @ObservedObject 之间的选择取决于谁拥有对象。如果视图创建对象——使用 @StateObject。如果视图接收现成对象——使用 @ObservedObject。这条规则非常重要,以至于 Xcode 在子视图(通过初始化器接收对象)中使用 @StateObject 时会发出警告。

情况推荐的 wrapper
视图通过 ViewModel() 创建模型@StateObject
视图从父级接收模型@ObservedObject
模型在一个视图中使用@StateObject
模型通过 Environment 传递@EnvironmentObject
模型需要用于预览@ObservedObject + mock

在实践中,项目开始时通常在根视图中使用 @StateObject,在所有子视图中使用 @ObservedObject。随着应用程序的增长,部分 @StateObject 可以替换为 @EnvironmentObject 以简化层级。然而,@StateObject 仍然是具有自己逻辑的模块化屏幕的最佳选择。

@StateObject 的使用模式

第一种模式——MVVM 与 @StateObject。ViewModel 作为 ObservableObject 在视图中通过 @StateObject 创建。ViewModel 包含 @Published 属性和业务逻辑。视图订阅变化并更新界面。这种方法提供了可测试的隔离:ViewModel 可以在没有 UI 的情况下通过直接创建实例进行测试。

第二种模式——带依赖的 @StateObject。如果 ViewModel 需要服务,请使用带参数的初始化。例如,@StateObject var viewModel = UserViewModel(api: APIClient.shared)。但要小心:参数在每次 body 渲染时都会被计算,但对象只创建一次。SwiftUI 忽略 @StateObject 的后续初始化。

第三种模式——嵌套的 @StateObject。在 SwiftUI 中,一个视图中可以有多个 @StateObject,但这很少合理。通常一个 @StateObject 负责视图的整个数据集。如果逻辑变得过于复杂,将其拆分为一个 @StateObject 内部的 @ObservedObject 服务组合。

swift
struct AppView: View {
    @StateObject var router = NavigationRouter()
    @StateObject var auth = AuthViewModel()

    var body: some View {
        ContentView()
            .environmentObject(router)
            .environmentObject(auth)
    }
}

在示例中,AppView 创建了两个 @StateObject:NavigationRouter 用于管理导航,AuthViewModel 用于身份验证。两个对象都通过 environmentObject 注入到 Environment 中。任何子视图都可以通过 @EnvironmentObject 访问它们,而无需通过初始化器链传递。

@StateObject 与带参数的初始化

@StateObject 支持带任意参数的初始化,但有一个重要特性:初始化器只调用一次。在 body 重复渲染时,参数的新值会被忽略。这意味着如果你将 @State var id: Int = 5 传递给 @StateObject var vm = ViewModel(id: id),当 id 改变时,ViewModel 不会收到新值。

为解决这个问题,使用 onReceive 或 onAppear 进行同步。通过 Combine 在 ViewModel 内部订阅参数变化,或者在视图级别通过 .onChange(of:) 方法传递参数。替代方案——如果对象需要动态响应外部变化,使用 @ObservedObject 代替 @StateObject。

swift
struct DetailView: View {
    let itemId: Int
    @StateObject var viewModel = DetailViewModel()

    var body: some View {
        Text(viewModel.title)
            .onAppear { viewModel.load(id: itemId) }
    }
}

正确的方法:DetailView 接收 itemId 作为 let 属性(通过结构体初始化器传递),@StateObject 创建不带参数的 DetailViewModel。在 onAppear 中调用 load(id:) 方法,该方法为传递的 ID 加载数据。这保证了 ViewModel 由 @StateObject 机制创建,但数据在每次视图出现时使用当前 ID 加载。

@StateObject 的常见错误

主要错误——在从父级接收对象的子视图中使用 @StateObject。如果 ParentView 创建了 @StateObject model,而 ChildView 声明了 @StateObject var model: ModelType(带默认参数),那么 ChildView 将创建自己的独立实例。父对象和子对象不会关联,一个对象的变化不会反映在另一个对象中。

第二个错误——在 List 或 ForEach 中放置 @StateObject。列表的每个元素都创建自己的 @StateObject,导致多个独立实例。对于列表,正确的方法是通过 @ObservedObject 将单个 ObservableObject 传递给所有元素,或者在 List 内部使用带有 @State 的 Identifiable 结构。

第三个问题——deinit 中缺少清理。@StateObject 在视图的整个生命周期中存在。如果对象创建了定时器、Combine 订阅或网络请求,deinit 必须取消它们。否则,内存泄漏和关闭屏幕后后台工作的继续是不可避免的。始终在 deinit 中使用 Combine Cancellable store 或 invalidate 定时器。

常见问题

@StateObject 在 SwiftUI 中是什么时候出现的?

@StateObject 是在 WWDC 2020 上随 SwiftUI 2.0 一起添加的,同时还有 iOS 14、macOS 11、watchOS 7 和 tvOS 14。在此之前,@ObservedObject 是使用 ObservableObject 的唯一方式,导致频繁的数据丢失错误。

@StateObject 可以是可选的(Optional)吗?

不,@StateObject 不支持 Optional 类型。对象必须在声明时初始化。如果需要可选对象,请使用 @ObservedObject 或 @EnvironmentObject 与可选类型一起使用。

如何检查 @StateObject 只创建了一次?

在 ObservableObject 的初始化器和 deinit 中添加 print(#function)。如果在重复渲染时 init 没有被调用——@StateObject 工作正常。如果 init 每次都被调用——将 @ObservedObject 替换为 @StateObject。

能否通过 UIHostingController 在 UIKit 中使用 @StateObject?

可以,@StateObject 在通过 UIHostingController 嵌入 UIKit 的 SwiftUI 视图中工作。对象的生命周期绑定到 SwiftUI 视图,而不是 UIViewController。如果 SwiftUI 视图被替换,@StateObject 被销毁。

哪一个更好:一个带有大型 ViewModel 的 @StateObject 还是多个小型的?

多个职责分离的小型 @StateObject。这提高了可测试性、可复用性和性能——当一个对象变化时,只有订阅的界面部分被重新绘制,而不是整个视图。

总结

  • @StateObject — 用于在视图中创建和拥有 ObservableObject 的 Property Wrapper
  • 唯一实例 — 对象在 body 重复渲染时不会重新创建
  • 真相来源 — 根视图中的 @StateObject 保证层级中数据的稳定性
  • 选择规则 — @StateObject 用于创建,@ObservedObject 用于接收现成对象
  • 初始化 — @StateObject 中的参数只计算一次,更新不被跟踪
  • Deinit — 在 ObservableObject 的 deinit 中必须清理定时器和订阅
  • iOS 14+ — @StateObject 从 iOS 14、macOS 11、watchOS 7、tvOS 14 起可用

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

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

讨论项目

另请阅读