SwiftUI 中 @Binding 和 @Bindable 的区别

前言

在 iOS 17 的操作系统上,Apple 的 SwiftUI 框架在处理数据的方式上做了一些改变:其推出了 @ObservableObject 和 @StateObject 的 Combine 版本,它采用了 @Observable宏(这是一个名为Observation 的新包的一部分)的形式。

一个有趣的新增功能是 @Bindable属性包装器。这个属性包装器与 SwiftUI 中的 @Binding 共存,它们的组合使用可以允许开发人员创建到可观察类属性的绑定。那么这些属性包装器的作用是什么呢?是什么使它们彼此不同?下面我们在接下来的内容中一起梳理一下。

@Binding

首先,我们来看一下 @Binding 属性包装器。当我们需要一个视图来改变另一个视图拥有的数据时,我们会使用 @Binding 创建一个绑定。例如,我们的绑定可以是这样的:

css 复制代码
struct MyButton: View {
    @Binding var count: Int

    var body: some View {
        Button(action: {
            count += 1
        }, label: {
            Text("加 1")
        })
    }
}

这个例子并没有什么特殊之处,但它说明了我们如何编写一个视图来读取和改变这个视图外部拥有的计数器。

数据所有权在 SwiftUI 中是一个非常重要的话题,它的属性包装器可以真正帮助我们理解谁拥有什么。在@Binding 的情况下,我们所知道的是:其他视图将为我们提供读取计数的能力,以及改变该计数器的方法。

每当用户点击 MyButton 时,计数器就会增加,视图也会更新。这包括最初拥有并使用该计数器的视图。

在 SwiftUI 中,绑定经常用于开箱即用的组件中。例如,TextField 需要绑定到视图拥有的 String 属性。这允许 TextField 读取视图拥有的值,并且 TextField 还可以根据用户的输入更新文本值。

那 @Bindable 又是用来做什么的呢?

@Bindable

如果你比较熟悉 iOS16 或更早版本的 SwiftUI,你会知道你可以创建绑定到 @State, @StateObject, @ObservedObject 和更多类似的对象。在 iOS 17 中,我们可以访问 @Observable宏,但它不能像 ObservableObject 那样创建绑定。相反,如果我们的 @Observable 对象是一个类,我们可以让视图把这个对象设置为可绑定的。

这意味着我们可以用 @Bindable 属性包装器标记一个包含 Observable 类实例的属性,允许我们创建到类实例属性的绑定。如果没有 @Bindable,我们就做不到:

swift 复制代码
@Observable
class MyCounter {
    var count = 0
}

struct ContentView: View {
    var counter: MyCounter = MyCounter()

    init() {
        print("init")
    }

    var body: some View {
        VStack {
            Text("The counter is \(counter.count)")
            // 编译报错:Cannot find '$counter' in scope
            MyButton(count: $counter.count)
        }
        .padding()
    }
}

当我们将 var counter 属性设置为 @Bindable 时,我们可以创建一个绑定到计数器的 count 属性:

swift 复制代码
@Observable
class MyCounter {
    var count = 0
}

struct ContentView: View {
    @Bindable var counter: MyCounter

    init() {
        print("init")
    }

    var body: some View {
        VStack {
            Text("The counter is \(counter.count)")
            MyButton(count: $counter.count)
        }
        .padding()
    }
}

需要注意的是,如果你的视图拥有 Observable 对象,你通常会用 @State 标记它,并在视图中创建对象实例。当你的 Observable 对象被标记为 @State 时,你就可以创建对该对象属性的绑定。这要归功于 @State 属性包装器注释。

然而,如果你的视图不拥有 Observable 对象,那么使用 @State 就不合适了。创建 @Bindable 属性包装器就是为了解决这种情况,它允许你创建到对象属性的绑定。

Bindable 的使用仅限于符合 Observable 协议的类。创建一个符合 Observable 的对象最简单的方法是使用 @Observable 宏。

总结

在这篇文章中,你了解到 @Binding 和 @Bindable 的主要区别在于它们的作用:

  • @Binding 属性包装器表明视图上的某些状态块由另一个视图拥有,并且你可以对底层数据进行读写访问。
  • @Bindable 属性包装器允许你为 Observable 类拥有的属性创建绑定。如前所述,@Bindable 仅限于符合 Observable 的类,而创建Observable 对象最简单的方法是使用 @Observable 宏。
相关推荐
东坡肘子7 天前
当 Build in Public 重新转入地下 -- 肘子的 Swift 周报 #157
人工智能·swiftui·swift
OliverQiu7 天前
开源一个 macOS 小工具:集中管理 ChatGPT 的网络启动配置
macos·chatgpt·swiftui·mac·proxy·codex·dmg
传奇开心果编程11 天前
【SwiftUI提高练中学】第9课 安全与隐私
swiftui·移动开发·swift·编程语言理论·开发范式·编程框架·声明式ui
传奇开心果编程12 天前
【声明式UI开发实用技术学与练】第8课 状态提升与下放
学习·flutter·react native·ui·swiftui·composer
东坡肘子12 天前
Swift Server,又多了一个 Google -- 肘子的 Swift 周报 #156
人工智能·swiftui·swift
传奇开心果编程13 天前
【现代声明式UI学与练】第4课 列表渲染与 key——如何高效渲染列表、key 的作用、列表重排时的状态保持
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程13 天前
【SwiftUI提高练中学】第8课 网络层架构:拦截器、重试、缓存与离线优先
学习·macos·ui·ios·swiftui
传奇开心果编程13 天前
【现代声明式UI学与练】第7课 组件通信——父传子、子传父、跨层级通信、状态提升与 Context
学习·flutter·react native·ui·swiftui·android jetpack
tink14 天前
告别 Xcode IDE:用 VS Code + SweetPad + XcodeGen 开发 iOS 应用的完整指南
ios·swiftui·xcode
传奇开心果编程14 天前
【现代声明式UI学与练】第9课 性能优化——渲染优化、列表优化、内存优化、启动优化
学习·flutter·react native·ui·性能优化·swiftui·android jetpack