《swiftUI进阶 第10章:现代状态管理(iOS 17+)》

8 阅读4分钟

10.1 @Observable 宏介绍

核心概念

@Observable 是 iOS 17+ 引入的宏,用于简化状态管理。它替代了传统的 ObservableObject + @Published 模式,提供了更简洁、更高效的状态管理方案。

基本使用


import SwiftUI

  


@Observable

class UserModel {

    var name = "张三"

    var age = 25

    var email = "zhangsan@example.com"

    

    func updateName(_ newName: String) {

        name = newName

    }

    

    func incrementAge() {

        age += 1

    }

}

  


struct ContentView: View {

    @State private var userModel = UserModel()

    

    var body: some View {

        VStack {

            Text("姓名: \(userModel.name)")

            Text("年龄: \(userModel.age)")

            Text("邮箱: \(userModel.email)")

            

            Button("修改姓名") {

                userModel.updateName("李四")

            }

            

            Button("增加年龄") {

                userModel.incrementAge()

            }

        }

        .padding()

    }

}

官方文档

根据苹果官方文档,@Observable 宏的核心特点:

  • 自动生成观察代码,无需手动实现 ObservableObject 协议

  • 所有属性默认都是可观察的,无需使用 @Published

  • 性能更优,避免了 @Published 的额外开销

  • 语法更简洁,减少了模板代码

10.2 @Bindable 双向绑定

核心概念

@Bindable 是与 @Observable 配合使用的属性包装器,用于创建双向绑定。

基本使用


import SwiftUI

  


@Observable

class Settings {

    var isDarkMode = false

    var fontSize = 16.0

    var notifications = true

}

  


struct ContentView: View {

    @State private var settings = Settings()

    

    var body: some View {

        VStack {

            Toggle("深色模式", isOn: $settings.isDarkMode)

            Slider(value: $settings.fontSize, in: 12...24)

            Toggle("通知", isOn: $settings.notifications)

            

            ChildView(settings: settings)

        }

        .padding()

        .preferredColorScheme(settings.isDarkMode ? .dark : .light)

    }

}

  


struct ChildView: View {

    @Bindable var settings: Settings

    

    var body: some View {

        VStack {

            Text("当前设置:")

            Text("深色模式: \(settings.isDarkMode ? "开启" : "关闭")")

            Text("字体大小: \(settings.fontSize, specifier: "%.1f")")

            Text("通知: \(settings.notifications ? "开启" : "关闭")")

            

            // 双向绑定

            Toggle("子视图修改深色模式", isOn: $settings.isDarkMode)

        }

        .padding()

        .background(Color.gray.opacity(0.1))

        .cornerRadius(8)

    }

}

与 @ObservedObject 的区别

在 SwiftUI 中管理状态时,新推出的 @Observable 方案与传统的 @ObservedObject 方案主要区别如下:

特性@Bindable + @Observable@ObservedObject + @Published
语法更简洁,无需 @Published需要为每个可观察属性添加 @Published
性能更优,直接访问属性间接访问,有额外开销
双向绑定直接使用 $ 前缀同样支持 $ 前缀
兼容性iOS 17+iOS 13+

提示:如果你的应用最低支持 iOS 17,推荐优先使用 @Observable;若需兼容旧版本,则仍用 @ObservedObject

10.3 @Observable 的优势与工作原理

主要优势

  1. 语法简洁:无需实现 ObservableObject 协议,无需为属性添加 @Published

  2. 性能提升:直接访问属性,避免了 @Published 的额外开销

  3. 类型安全:编译时检查,减少运行时错误

  4. 易于使用:与现有 SwiftUI 代码无缝集成

  5. 未来方向:苹果官方推荐的状态管理方案

工作原理

@Observable 宏在编译时会:

  1. 为类自动实现 Observable 协议

  2. 为所有属性生成观察代码

  3. 当属性值变化时,自动通知视图更新

  4. 支持 @Bindable 进行双向绑定

10.4 从 ObservableObject 迁移到 @Observable

迁移步骤

  1. 替换 ObservableObject 为 @Observable

  2. 移除 @Published 属性包装器

  3. 更新视图中的使用方式

  4. 使用 @Bindable 进行双向绑定

迁移示例

旧代码(ObservableObject):


import SwiftUI

import Combine

  


class UserViewModel: ObservableObject {

    @Published var name = "张三"

    @Published var age = 25

    

    func updateName(_ newName: String) {

        name = newName

    }

}

  


struct ContentView: View {

    @StateObject private var viewModel = UserViewModel()

    

    var body: some View {

        VStack {

            Text(viewModel.name)

            Button("修改姓名") {

                viewModel.updateName("李四")

            }

        }

    }

}

新代码(@Observable):


import SwiftUI

  


@Observable

class UserViewModel {

    var name = "张三"

    var age = 25

    

    func updateName(_ newName: String) {

        name = newName

    }

}

  


struct ContentView: View {

    @State private var viewModel = UserViewModel()

    

    var body: some View {

        VStack {

            Text(viewModel.name)

            Button("修改姓名") {

                viewModel.updateName("李四")

            }

        }

    }

}

注意事项

  1. 兼容性@Observable 仅在 iOS 17+ 可用

  2. 性能:对于简单模型,性能提升明显

  3. 迁移成本:对于现有项目,需要评估迁移成本

  4. 混合使用:可以在同一项目中混合使用两种方式

最佳实践

  1. 新项目:优先使用 @Observable 进行状态管理

  2. 现有项目:如果支持 iOS 17+,考虑逐步迁移

  3. 复杂状态:对于复杂的状态管理,仍然可以使用 ObservableObject

  4. 性能优化:对于频繁更新的属性,使用 @Observable 可以获得更好的性能

实战:现代状态管理示例


import SwiftUI

  


// 数据模型

@Observable

class TodoModel {

    struct Todo: Identifiable {

        let id = UUID()

        var title: String

        var isCompleted = false

    }

    

    var todos: [Todo] = [

        Todo(title: "学习 SwiftUI"),

        Todo(title: "构建项目"),

        Todo(title: "测试应用")

    ]

    

    var newTodoTitle = ""

    

    func addTodo() {

        guard !newTodoTitle.isEmpty else { return }

        todos.append(Todo(title: newTodoTitle))

        newTodoTitle = ""

    }

    

    func toggleTodo(_ todo: Todo) {

        if let index = todos.firstIndex(where: { $0.id == todo.id }) {

            todos[index].isCompleted.toggle()

        }

    }

    

    func deleteTodo(_ todo: Todo) {

        todos.removeAll { $0.id == todo.id }

    }

}

  


// 主视图

struct ContentView: View {

    @State private var todoModel = TodoModel()

    

    var body: some View {

        NavigationStack {

            VStack {

                HStack {

                    TextField("添加待办事项", text: $todoModel.newTodoTitle)

                        .textFieldStyle(.roundedBorder)

                    Button("添加") {

                        todoModel.addTodo()

                    }

                    .disabled(todoModel.newTodoTitle.isEmpty)

                }

                .padding()

                

                List {

                    ForEach(todoModel.todos) { todo in

                        HStack {

                            Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")

                                .foregroundColor(todo.isCompleted ? .green : .gray)

                                .onTapGesture {

                                    todoModel.toggleTodo(todo)

                                }

                            Text(todo.title)

                                .strikethrough(todo.isCompleted)

                        }

                    }

                    .onDelete { indices in

                        indices.forEach { index in

                            todoModel.deleteTodo(todoModel.todos[index])

                        }

                    }

                }

            }

            .navigationTitle("待办事项")

        }

    }

}

性能对比

除了基础语法和兼容性,两者在不同场景下的实际表现差异如下:

场景@ObservableObservableObject
简单属性更新更快,直接访问稍慢,通过发布者
复杂对象更新更快,无需额外开销稍慢,需要发布通知
内存使用更低更高
代码复杂度

总结@Observable 在性能、内存和开发效率上都更优,如果你的最低部署目标已是 iOS 17,这就是更现代的选择。

高级技巧

组合使用 @Observable 和 Environment


import SwiftUI

  


@Observable

class AppState {

    var user: User?

    var isLoggedIn = false

    var theme: Theme = .light

    

    enum Theme {

        case light, dark, system

    }

}

  


struct User {

    let id: UUID

    let name: String

    let email: String

}

  


@main

struct MyApp: App {

    @State private var appState = AppState()

    

    var body: some Scene {

        WindowGroup {

            ContentView()

                .environment(appState)

        }

    }

}

  


struct ContentView: View {

    @Environment(AppState.self) private var appState

    

    var body: some View {

        if appState.isLoggedIn {

            Text("欢迎,\(appState.user?.name ?? "用户")!")

        } else {

            LoginView()

        }

    }

}

  


struct LoginView: View {

    @Environment(AppState.self) private var appState

    @State private var email = ""

    @State private var password = ""

    

    var body: some View {

        VStack {

            TextField("邮箱", text: $email)

            SecureField("密码", text: $password)

            Button("登录") {

                // 模拟登录

                appState.user = User(id: UUID(), name: "张三", email: email)

                appState.isLoggedIn = true

            }

        }

        .padding()

    }

}

自定义可观察对象


import SwiftUI

  


@Observable

class DataService {

    var data: [String] = []

    var isLoading = false

    var error: String? = nil

    

    func loadData() async {

        isLoading = true

        error = nil

        

        do {

            // 模拟网络请求

            try await Task.sleep(nanoseconds: 1_000_000_000)

            data = ["数据1", "数据2", "数据3"]

        } catch {

            self.error = "加载失败"

        } finally {

            isLoading = false

        }

    }

}

  


struct ContentView: View {

    @State private var dataService = DataService()

    

    var body: some View {

        VStack {

            if dataService.isLoading {

                ProgressView()

            } else if let error = dataService.error {

                Text(error)

                    .foregroundColor(.red)

            } else {

                List(dataService.data, id: \.self) {

                    Text($0)

                }

            }

            

            Button("加载数据") {

                Task {

                    await dataService.loadData()

                }

            }

        }

        .padding()

    }

}

总结

现代状态管理(iOS 17+)为 SwiftUI 应用带来了更简洁、更高效的状态管理方案:

  • @Observable 宏:自动生成观察代码,无需 ObservableObject@Published

  • @Bindable:与 @Observable 配合使用,实现双向绑定

  • 性能提升:直接访问属性,避免额外开销

  • 语法简洁:减少模板代码,提高开发效率

  • 官方推荐:苹果官方推荐的状态管理方案

对于 iOS 17+ 项目,建议优先使用 @Observable 进行状态管理,它将成为未来 SwiftUI 开发的主流选择。