这是一个将 @State, @Binding, @StateObject, @ObservedObject, @EnvironmentObject, 和 @AppStorage 全部串联起来的完整示例。

场景模拟:一个简单的“用户仪表盘” App

在这个 App 中:

  1. 全局环境:有一个 UserSession(用户登录状态),全 App 共享。
  2. 本地设置:有一个“通知开关”,直接存入 UserDefaults。
  3. 独立页面逻辑:有一个计时器页面,拥有自己的 ViewModel。
  4. 父子传值:父页面控制弹窗,子页面修改父页面的标题。

1. 定义数据模型 (Model & ViewModel)

首先定义两个类,一个用于全局共享,一个用于特定页面。

import SwiftUI
import Combine

// 1. 全局共享对象 (类似于你的 SyncSettingsViewModel)
// 场景:需要在任何页面都能访问到的数据
class UserSession: ObservableObject {
    @Published var username: String = "Guest"
    @Published var isLoggedIn: Bool = false
}

// 2. 局部页面专用的 ViewModel
// 场景:只服务于“计时器”这个功能
class TimerViewModel: ObservableObject {
    @Published var seconds: Int = 0
    private var timer: AnyCancellable?

    func start() {
        if timer != nil { return }
        timer = Timer.publish(every: 1, on: .main, in: .common)
            .autoconnect()
            .sink { _ in self.seconds += 1 }
    }

    func stop() {
        timer?.cancel()
        timer = nil
    }
}

2. 视图层级 (Views)

请仔细看注释,每一处注解的选择都有其特定的理由。

// 入口:注入环境对象
struct MyApp: App {
    // 创建唯一的全局实例
    @StateObject private var userSession = UserSession()

    var body: some Scene {
        WindowGroup {
            DashboardView()
                .environmentObject(userSession) // 注入环境,之后所有子 View 都能用
        }
    }
}

// --- 主视图 ---
struct DashboardView: View {
    // A. @EnvironmentObject: 从“空气”中抓取刚才注入的 session
    @EnvironmentObject var session: UserSession

    // B. @AppStorage: 自动读写 UserDefaults,持久化存储
    @AppStorage("enableNotifications") var enableNotifs: Bool = true

    // C. @State: 管理当前 View 的 UI 状态 (是否显示编辑弹窗)
    @State private var showEditSheet = false

    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                Text("欢迎, \(session.username)")
                    .font(.largeTitle)

                // 使用 AppStorage 的开关,重启 App 后状态依然保留
                Toggle("接收通知", isOn: $enableNotifs)
                    .padding()

                NavigationLink("进入计时器页面") {
                    // 跳转到子页面
                    TimerPageView()
                }

                Button("修改用户名") {
                    showEditSheet = true
                }
            }
            .padding()
            .sheet(isPresented: $showEditSheet) {
                // 传递 Binding 给子视图
                EditNameView(name: $session.username)
            }
        }
    }
}

// --- 子视图 1:拥有独立逻辑的页面 ---
struct TimerPageView: View {
    // D. @StateObject: 我是这个 ViewModel 的“主人”
    // 这个 View 初始化时,创建 ViewModel。View 刷新时,它不会死。
    @StateObject private var timerVM = TimerViewModel()

    var body: some View {
        VStack {
            Text("计时: \(timerVM.seconds) 秒")
                .font(.title)

            HStack {
                Button("开始") { timerVM.start() }
                Button("停止") { timerVM.stop() }
            }

            Divider()

            // 把 VM 传给更小的子组件
            TimerDisplayView(vm: timerVM)
        }
    }
}

// --- 子视图 2:复用组件 ---
struct TimerDisplayView: View {
    // E. @ObservedObject: 我只是“观察者”
    // 我不创建它,我只接收父视图传下来的实例。
    @ObservedObject var vm: TimerViewModel

    var body: some View {
        Text("子组件也在监听: \(vm.seconds)")
            .foregroundColor(.gray)
            .font(.caption)
    }
}

// --- 子视图 3:修改弹窗 ---
struct EditNameView: View {
    // F. @Binding: “传声筒”
    // 我不存数据,我修改的是 DashboardView 里的 session.username
    @Binding var name: String
    @Environment(\.dismiss) var dismiss // 系统环境值,用于关闭弹窗

    var body: some View {
        VStack {
            TextField("输入新名字", text: $name)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()

            Button("完成") {
                dismiss()
            }
        }
        .padding()
    }
}

3. 代码逻辑大复盘

代码位置 使用的注解 为什么选它? 这里的行为
MyApp @StateObject 创建全局唯一的 UserSession 源头。 保证 App 生命周期内 session 活着。
DashboardView @EnvironmentObject UserSession 已经在 MyApp 注入了,这里直接取。 如果在其他页面改了名字,这里会自动更新。
DashboardView @AppStorage 需要记住用户的“通知设置”,即使杀进程也要记住。 自动存取 UserDefaults 中的 "enableNotifications"。
DashboardView @State 控制弹窗 (showEditSheet) 是纯 UI 逻辑,只属于当前页。 只有这个 View 关心弹窗是开是关。
EditNameView @Binding 弹窗需要修改父视图的数据,但不需要拥有数据。 子视图输入框变动 -> 父视图数据变动。
TimerPageView @StateObject 这个页面负责创建计时器逻辑 (TimerViewModel)。 页面存在时,计时器就存在;页面销毁,计时器销毁。
TimerDisplayView @ObservedObject 它只负责显示,借用父视图创建好的 ViewModel。 跟着 TimerViewModel 的变化刷新 UI。

下一步建议

这个示例几乎涵盖了 95% 的 SwiftUI 开发场景。

如果你的项目是用 iOS 17 开发的,你还可以使用更现代的 Observation 框架@Observable 宏),它把 @StateObject@ObservedObject@EnvironmentObject 的写法全部统一简化了。

你想看看如果用 iOS 17 的新写法,这段代码会简化成什么样吗?