Swift 5.9 与 SwiftUI 5.0 中新 Observation 框架应用之深入浅出

0. 概览
Swift 5.9 一声炮响为我们带来全新的宏(Macro)机制,也同时带来了干霄凌云的 Observation 框架。

Observation 框架可以增强通用场景下的使用,也可以搭配 SwiftUI 5.0 而获得双剑合璧的更强威力。
在本篇博文,您将学到如下内容:
- 0. 概览
- 1. @Observable 宏
- 2. 通用情境下如何观察 Observable 对象?
- 3. Observable 对象与 SwiftUI 珠联璧合
- 4. 被“抛弃的” @EnvironmentObject
- 5. 在视图中将不可变 Observable 对象转换为可变对象的妙招
- 6. 总结
那么,就让我们赶快进入 Observation 奇妙的世界吧!
Let‘s go!!!😉
1. @Observable 宏
简单来说,Observation 框架为我们提供了集鲁棒性(robust)、安全性、高性能等三大特性为一身的 Swift 全新观察者设计模式。
它的核心功能在于:监视对象状态,并在改变时做出反应!

在 Swift 5.9 中,我们可以非常轻松的通过 @Observable 宏将普通类“转化为”可观察(Observable)类。自然,它们的实例都是可观察的:
@Observable
final class Hero {var name: Stringvar power: Intinit(name: String, power: Int) {self.name = nameself.power = power}
}@Observable
final class Model {var title: Stringvar createAt: Date?var heros: [Hero]init(title: String, heros: [Hero]) {self.title = titleself.createAt = Date.nowself.heros = heros}
}
如上代码所示,我们定义了两个可观察类 Model 和 Hero,就是这么简单!
2. 通用情境下如何观察 Observable 对象?
在一个对象成为可观察之后,我们可以通过 withObservationTracking() 方法随时监听它状态的改变:

我们可以将对象需要监听的属性放在 withObservationTracking() 的 apply 闭包中,当且仅当( Hero 中其它属性的改变不予理会)这些属性发生改变时其 onChange 闭包将会被调用:
let hero = Hero(name: "大熊猫侯佩", power: 5)func watching() {withObservationTracking({NSLog("力量参考值:\(hero.power)")}, onChange: {NSLog("改变之前的力量!:\(hero.power)")watching()})
}watching()hero.name = "地球熊猫"
hero.power = 11
hero.power = 121
以上代码输出如下:

使用 withObservationTracking() 方法有 3 点需要注意:
- 它默认只会被调用 1 次,所以上面为了能够重复监听,我们在 onChange 闭包里对 watching() 方法再次进行了调用;
- withObservationTracking() 方法的 apply 闭包不管如何都会被调用 1 次,即使其监听的属性从未改变过;
- 在监听闭包中只能得到属性改变前的旧值;
目前,上面测试代码在 Xcode 15 的 Playground 中编译会报错,提示如下:
error: test15.playground:8:13: error: external macro implementation type ‘ObservationMacros.ObservableMacro’ could not be found for macro ‘Observable()’
final class Hero {
^Observation.Observable:2:180: note: ‘Observable()’ declared here
@attached(member, names: named(_$observationRegistrar), named(access), named(withMutation)) @attached(memberAttribute) @attached(extension, conformances: Observable) public macro Observable() = #externalMacro(module: “ObservationMacros”, type: “ObservableMacro”)
小伙伴们可以把它们放在 Xcode 的 Command Line Tool 项目中进行测试:

3. Observable 对象与 SwiftUI 珠联璧合
要想发挥 Observable 对象的最大威力,我们需要 SwiftUI 来一拍即合。
在 SwiftUI 中,我们无需再显式调用 withObservationTracking() 方法来监听改变,如虎添翼的 SwiftUI 已为我们自动完成了所有这一切!
struct ContentView: View {let model = Model(title: "地球超级英雄", heros: [])var body: some View { NavigationStack {Form {LabeledContent(content: {Text(model.title)}, label: {Text("藏匿点名称")})LabeledContent(content: {Text(model.createAt?.formatted(date: .omitted, time: .standard) ?? "无")}, label: {Text("更新时间")})Button("刷新") {// SwiftUI 会自动监听可观察对象的改变,并刷新界面model.title = "爱丽丝仙境兔子洞"model.createAt = Date.now}}.navigationTitle(model.title)}}
}
注意,上面代码中 model 属性只是一个普通的 let 常量,即便如此 model 的改变仍会反映到界面上:

4. 被“抛弃的” @EnvironmentObject
有了 Swift 5.9 中新 Observation 框架加入游戏,在 SwiftUI 5.0 中 EnvironmentObject 再无用武之地,我们仅用 Environment 即可搞定一切!
早在 SwiftUI 1.0 版本时,其就已经提供了 Environment 对应的构造器:
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen @propertyWrapper public struct Environment<Value> : DynamicProperty {...}
有了新 Observation 框架的入驻,结合其 Observable 可观察对象,Environment 可以再次大放异彩:
struct HeroListView: View {@Environment(Model.self) var modelvar body: some View {List(model.heros) { hero inHStack {Text(hero.name).font(.headline)Spacer()Text("\(hero.power)").font(.subheadline).foregroundStyle(.gray)}}}
}struct ContentView: View {@State var model = Model(title: "地球超级英雄", heros: [.init(name: "大熊猫侯佩", power: 5),.init(name: "孙悟空", power: 1000),.init(name: "哪吒", power: 511)])var body: some View { NavigationStack {Form {NavigationLink(destination: HeroListView().environment(model)) {Text("查看所有英雄")}}.navigationTitle(model.title)}}
}
现在,即使跨越多重层级关系我们也可以只通过 @Environment 而不用 @EnvironmentObject 来完成状态的间接传递了,是不是很赞呢?👍🏻
5. 在视图中将不可变 Observable 对象转换为可变对象的妙招
介绍了以上这许多,就还剩一个主题没有涉及:**Observable 对象的可变性!
**
为了能够在子视图中更改对应的可观察对象,我们可以用 @Bindable 修饰传入的 Observable 对象:
struct HeroView: View {@Bindable var hero: Herovar body: some View {Form {TextField("名称", text: $hero.name)TextField("力量", text: .init(get: {String(hero.power) }, set: {hero.power = Int($0) ?? 0}))}}
}
不过,对于之前 @Environment 那个例子来说,如何达到子视图能够修改传入的 @Environment 可观察对象呢?
别急,我们可以利用称为“临时可变(Temporary Variable)”的技术将原先不可变的可观察对象改为可变:
extension Hero: Identifiable {var id: String {name}
}struct HeroListView: View {@Environment(Model.self) var modelvar body: some View {// 在 body 内将 model 改为可变@Bindable var model = modelVStack {List(model.heros) { hero inHStack {Text(hero.name).font(.headline)Spacer()Text("\(hero.power)").font(.subheadline).foregroundStyle(.gray)}}.safeAreaInset(edge: .bottom) {// 绑定可变 model 中的状态以修改英雄名称TextField("", text: $model.heros[0].name).padding()} }}
}
运行效果如下:

“临时可变”这一技术可以用于视图中任何化“不变”为“可变”的场景中,当然对于直接视图间对象的传递,我们可以使用 @Bindable 这一更为“正统”的方法。
6. 总结
在本篇博文中,我们讨论了在 Swift 5.0 和 SwiftUI 5.0 中大放异彩 Observation 框架的使用,并就诸多技术细节问题给与了详细的介绍,愿君喜欢。
感谢观赏,再会!😎
相关文章:
Swift 5.9 与 SwiftUI 5.0 中新 Observation 框架应用之深入浅出
0. 概览 Swift 5.9 一声炮响为我们带来全新的宏(Macro)机制,也同时带来了干霄凌云的 Observation 框架。 Observation 框架可以增强通用场景下的使用,也可以搭配 SwiftUI 5.0 而获得双剑合璧的更强威力。 在本篇博文,…...
【已解决】在 Vite 项目中使用 eslint-config-ali 时遇到的解析错误
错误还原 搭建 Vite 项目 pnpm create vite my-vue-app --template vue-ts安装 eslint-config-ali pnpm i -D eslint-config-ali typescript-eslint/parser typescript-eslint/eslint-plugin eslint-plugin-import eslint-import-resolver-typescript vue-eslint-parser esl…...
蓝桥杯每日一题2023.10.5
3420. 括号序列 - AcWing题库 题目描述 题目分析 对于这一我们需要有前缀知识完全背包 完全背包的朴素写法: #include<bits/stdc.h> using namespace std; const int N 1010; int n, m, v[N], w[N], f[N][N]; int main() {cin >> n >> m;fo…...
PyTorch实例:简单线性回归的训练和反向传播解析
文章目录 🥦引言🥦什么是反向传播?🥦反向传播的实现(代码)🥦反向传播在深度学习中的应用🥦链式求导法则🥦总结 🥦引言 在神经网络中,反向传播算法…...
Arcgis提取玉米种植地分布,并以此为掩膜提取遥感影像
Arcgis提取玉米种植地分布上,并以此为掩膜提取遥感影像 一、问题描述 因为之前反演是整个研究区,然而土地利用类型有很多类,只在农田或者植被上进行反演,需要去除水体、建筑等其他类型,如何处理得到下图中只有耕地类…...
软件工程与计算总结(四)项目管理基础
目录 一.项目和项目管理 二.团队组织与管理 三.软件质量保障 四.软件配置管理 五.项目实践 一.项目和项目管理 1.软件开发远不是纯粹的编程,随着软件规模的增长,软件开发活动也变得越来越复杂~ 2.软件项目就是要将所有的软件开发活动组织起来&#…...
【Python】datetime 库
# timedelta(days, seconds, microseconds,milliseconds, minutes, hours, weeks) 默认按顺序传递参数 # 主要介绍 datetime.datetime 类 # 引入 from datetime import datetime today datetime.now() # 获取当前时间 2023-10-05 15:58:03.218651 today1 datetime.utcnow() #…...
从0开始python学习-28.selenium 需要图片验证的登录
url https://test.com/login driver.get(url) # 获取登录页面需要输入账号密码进行模拟登录操作 user driver.find_element(By.XPATH,//*[id"login"]/div[2]/div/form[2]/div[2]/div/div/input).send_keys(username) pwd driver.find_element(By.XPATH,//*[id&qu…...
Nginx搭建Rtmp流媒体服务,并使用Ffmpeg推流
文章目录 1.rtmp流媒体服务框架图2.nginx配置3.配置nginx4.使用ffmpeg推流5.实时推摄像头流 本项目在开发板上使用nginx搭建流媒体服务,利用ffmpeg进行推流,在pc上使用vlc media进行拉流播放。 1.rtmp流媒体服务框架图 2.nginx配置 下载:wge…...
IDEA 将一个普通Java工程转化为maven工程
打开IntelliJ IDEA并打开Java工程。 在项目窗口中,右键单击项目名称,选择“Add Framework Support”。 在弹出的窗口中,选择“Maven”。 在“Maven Information”窗口中,填写Group Id、Artifact Id和Version等基本信息。 点击…...
linux下的永久保存行号
linux下的永久保存行号 1.首先 这里是引用 输入命令:vi ~/.vimrc 其次 这里是引用 输入命令 set number...
92岁高龄的创始人张忠谋谈台积电发展史
一、张忠谋和台积电 在台北一间办公室里,张忠谋最近拿出一本印有彩色图案的旧书。它的标题是《VLSI 系统导论》,这是一本研究生水平的教科书,描述了计算机芯片设计的复杂性。92岁的张先生满怀敬意地举起它。 92岁高龄的台积电创始人张忠谋 “…...
【VIM】VIm初步使用
玩转Vim-从放弃到入门_哔哩哔哩_bilibili...
教育类《中学政史地》收稿方向-投稿邮箱
教育类《中学政史地》收稿方向-投稿邮箱 《中学政史地》收稿方向:中学政治、历史、地理类稿件 《中学政史地》创办于1987年,是我国唯一一份集中学政治、历史、地理三门学科为一体的综合性月刊。每月两期,分初中版和高中版。以服务学生、服务…...
数据库的备份与恢复
数据备份的重要性 备份的主要目的是灾难恢复。 在生产环境中,数据的安全性至关重要。 任何数据的丢失都可能产生严重的后果。 造成数据丢失的原因: 程序错误人为操作错误运算错误磁盘故障灾难(如火灾、地震)和盗窃 数据库备份…...
string类的模拟实现(万字讲解超详细)
目录 前言 1.命名空间的使用 2.string的成员变量 3.构造函数 4.析构函数 5.拷贝构造 5.1 swap交换函数的实现 6.赋值运算符重载 7.迭代器部分 8.数据容量控制 8.1 size和capacity 8.2 empty 9.数据修改部分 9.1 push_back 9.2 append添加字符串 9.3 运算符重载…...
C 函数指针
就像指针可以指向一般变量、数组、结构体那样,指针也可以指向函数。 函数指针的主要用途是向其他函数传递“回调”,或者模拟类和对象。 形式如下: int (*POINTER_NAME)(int a, int b) 这类似于指向数组的指针可以表示所指向的数组。指向函数…...
zkVM设计性能分析
1. 引言 本文主要参考: 2023年9月ZKSummit10 Wei Dai 1k(x) & Terry Chung 1k(x)分享视频 ZK10: Analysis of zkVM Designs - Wei Dai & Terry Chung 当前有各种zkVM,其设计思想各有不同,且各有取舍,本文重点对现有各z…...
调用gethostbyname实现域名解析(附源码)
VC常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...&a…...
面向无线传感器网络WSN的增强型MODLEACH设计与仿真(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
