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 的优势与工作原理
主要优势
-
语法简洁:无需实现
ObservableObject协议,无需为属性添加@Published -
性能提升:直接访问属性,避免了
@Published的额外开销 -
类型安全:编译时检查,减少运行时错误
-
易于使用:与现有 SwiftUI 代码无缝集成
-
未来方向:苹果官方推荐的状态管理方案
工作原理
@Observable 宏在编译时会:
-
为类自动实现
Observable协议 -
为所有属性生成观察代码
-
当属性值变化时,自动通知视图更新
-
支持
@Bindable进行双向绑定
10.4 从 ObservableObject 迁移到 @Observable
迁移步骤
-
替换 ObservableObject 为 @Observable
-
移除 @Published 属性包装器
-
更新视图中的使用方式
-
使用 @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("李四")
}
}
}
}
注意事项
-
兼容性:
@Observable仅在 iOS 17+ 可用 -
性能:对于简单模型,性能提升明显
-
迁移成本:对于现有项目,需要评估迁移成本
-
混合使用:可以在同一项目中混合使用两种方式
最佳实践
-
新项目:优先使用
@Observable进行状态管理 -
现有项目:如果支持 iOS 17+,考虑逐步迁移
-
复杂状态:对于复杂的状态管理,仍然可以使用
ObservableObject -
性能优化:对于频繁更新的属性,使用
@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("待办事项")
}
}
}
性能对比
除了基础语法和兼容性,两者在不同场景下的实际表现差异如下:
| 场景 | @Observable | ObservableObject |
|---|---|---|
| 简单属性更新 | 更快,直接访问 | 稍慢,通过发布者 |
| 复杂对象更新 | 更快,无需额外开销 | 稍慢,需要发布通知 |
| 内存使用 | 更低 | 更高 |
| 代码复杂度 | 低 | 高 |
总结:
@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 开发的主流选择。