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

为什么SwiftUI中@State与@ObservedObject处理简单状态变化表现不同?

核心差异原因

这个行为差异本质是SwiftUI对@State和@ObservedObject两类状态源的重渲染策略不同导致的:

  • @State是SwiftUI框架内部托管存储的属性,它的存储位置和当前View的生命周期绑定,不属于View结构体本身的成员值。当@State修饰的selectedTab变化时,SwiftUI只会触发ContentView的body局部增量重计算,重计算过程中会自动对比子View的结构差异,对于没有任何入参变更、标识稳定的OneView和TwoView,SwiftUI会直接复用之前创建的实例,不会重新调用它们的init方法。
  • @ObservedObject对应的是外部存储的可观察对象,当它的@Published属性变更时,会通过objectWillChange发布者通知SwiftUI,触发整个依赖该对象的View树全量重计算。此时SwiftUI会重新构造body内的所有子View实例,哪怕子View没有任何入参变化,也会执行init方法。

可选优化方案

如果需要用@ObservedObject管理选中状态同时避免Tab对应的View重复初始化,可以给子View添加固定的唯一标识,告知SwiftUI该View的标识未发生变化,可以复用实例:

OneView()
  .tabItem { Text("One") }
  .tag(Tab.one)
  .id(Tab.one) // 固定标识

也可以用EquatableView包装子View,让SwiftUI根据相等性判断是否需要重新创建实例。

复现代码

可直接在Playground运行的复现代码如下,切换两个TabView初始化方法的注释即可复现差异:

import SwiftUI
import PlaygroundSupport

enum Tab: String {
  case one
  case two
}

struct ContentView: View {

  @State var selectedTab: Tab = .one
  @ObservedObject var selectedTabObject: SelectedTabAsObject

  init(selectedTabObject: SelectedTabAsObject) {
    self.selectedTabObject = selectedTabObject
    print("Init called for ContentView!")
  }

  var body: some View {
// TODO: 切换下面两行的注释即可复现差异
    TabView(selection: $selectedTab) {
//    TabView(selection: $selectedTabObject.selectedTab) {
      OneView()
        .tabItem {
          Text("One")
        }
        .tag(Tab.one)
      TwoView()
        .tabItem {
          Text("Two")
        }
        .tag(Tab.two)
    }
  }
}

class SelectedTabAsObject: ObservableObject {
  @Published var selectedTab: Tab = .one

  init() {
    print("Init called for SelectedTabAsObject!")
  }
}

struct OneView: View {

  init() {
    print("Init called for OneView!")
  }

  var body: some View {
    Text("One View")
  }
}

struct TwoView: View {
  var body: some View {
    Text("Two View")
  }
}


struct AppView: View {

  let selectedTabObject: SelectedTabAsObject

  init() {
    self.selectedTabObject = SelectedTabAsObject()
    print("Init called for AppView!")
  }

  var body: some View {
    ContentView(selectedTabObject: selectedTabObject)
  }
}

PlaygroundPage.current.setLiveView(AppView())

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 11:54:03