Swift 5.9 新 @Observable 对象在 SwiftUI 使用中的陷阱与解决

在这里插入图片描述

概览

在 Swift 5.9 中,苹果为我们带来了全新的可观察框架 Observation,它是观察者开发模式在 Swift 中的一个全新实现。

在这里插入图片描述

除了自身本领过硬以外,Observation 框架和 SwiftUI 搭配起来也能相得益彰,事倍功半。不过 Observable 对象在 SwiftUI 中干起活来可得特别注意,稍不留神结果就会出乎秃头码农们的意料之外。

在本篇博文中,您将学到如下内容:

  • 概览
  • 1. 什么是 Observable 对象?
  • 2. SwiftUI 中对于 Observable 对象承载的两种方式
  • 3. “原形毕露?”
  • 4. 溯本回原
  • 总结

闲言少叙,让我们马上开始 Observable 的探险之旅吧!Let‘s go!!!😉


1. 什么是 Observable 对象?

Observable 对象(不同于之前的 ObservedObject 对象)是 Swift 5.9 新 Observation 框架中推出的一种原生可观察对象。

在这里插入图片描述

Observation 框架在 Swift 中提供了观察者设计模式的一个健壮、类型安全和高性能的实现。该模式允许可观察对象维护观察者列表,并通知它们特定属性的改变。这样做的优点是可以实现对象的低耦合,并允许在潜在多个观察者之间隐式分布更新。

创建 Observable 对象很简单,我们只需用 @Observable 宏修饰对应类的定义,该类的实例即为 Observable 对象:

@Observable
class Foo {var name: Stringvar age: Intvar power: Doubleinit(name: String, age: Int, power: Double) {self.name = nameself.age = ageself.power = power}
}// 创建 Observable 对象 foo
let foo = Foo(name: "hopy", age: 11, power: 5)

2. SwiftUI 中对于 Observable 对象承载的两种方式

在 SwiftUI 中,我们可以同样用 @State 属性包装器来对 Observable 对象声明“真相之源”:

@Observable
class Model {var value: Intinit(_ value: Int) {self.value = value}
}struct ContentView: View {@State var model = Model(11)var body: some View {NavigationStack {VStack {Text("value: \(model.value)")Button("add 1") {model.value += 1}}}}
}

大家可以看到,@Observable 对象的行为和之前的 ObservableObject 对象如出一辙,其内容的更改也会导致界面的刷新:

在这里插入图片描述

不过,与之前旧 ObservedObject 对象所不同的是,@Observable 对象在 SwiftUI 中无需显式用属性包裹器(Property Wrapper)修饰也能及时的根据变化刷新视图:

struct ContentView: View {    let model = Model(11)var body: some View {NavigationStack {VStack {Text("value: \(model.value)")Button("add 1") {model.value += 1}}}}
}

在上面的代码中,我们的 model 对象没有任何修饰,只是一个单纯的 let 属性。不过运行可以发现,它的结果和之前一毛一样!

这难道意味着在 SwiftUI 中用 @State 或 let 来定义 @Observable 对象没有任何区别么?

非也非也!

3. “原形毕露?”

我们知道在 SwiftUI 中视图其实都是状态的函数。但状态不仅仅是视图的简单附庸,它们又可以超然于视图之外。

简单来说:当视图 body 被重新“求值”时,非状态值会被重建,但状态不会!因为状态的生成周期被放在一个单独的存储区内,和视图本身是分开的。

struct SubView: View {let model = Model(0)var body: some View {RoundedRectangle(cornerRadius: 15.0).fill(.red).frame(width: 150, height: 150).overlay {VStack {Text("\(model.value)")}}.onTapGesture {model.value += 1}.foregroundStyle(.white)}
}struct SubStateView: View {@State var model = Model(0)var body: some View {RoundedRectangle(cornerRadius: 15.0).fill(.green).frame(width: 150, height: 150).foregroundStyle(.white).overlay {VStack {Text("\(model.value)")}}.onTapGesture {model.value += 1}.foregroundStyle(.white)}
}struct ContentView: View {@State var id = Int.random(in: 0...10000)var body: some View {NavigationStack {VStack {GroupBox("无状态 @Observable 对象") {SubView()}GroupBox("@State @Observable 对象") {SubStateView()}Button("刷新:\(id)") {id = Int.random(in: 0...10000)}}.padding().font(.largeTitle.weight(.black)).navigationTitle("@Observable 对象演示")}}
}

对于上面的代码来说,我们在主视图中创建了两个子视图:SubView 和 SubStateView。其中 @Observable 对象 model 在前者中不以状态承载,而在后者中作为状态承载。

在这里插入图片描述

运行结果如上图所示:当用户点击刷新按钮时会引起主视图中 Text 显示内容的改变,从而导致主视图中两个子视图发生重建。可以看到以状态承载的 @Observable 对象保持稳定,而另一个 @Observable 对象被重建了。

4. 溯本回原

从上面我们知道了问题的症结,所以改善起来就很简单了:我们只需要保持 @Observable 对象本身的稳定性即可。

一种办法是在主视图中以状态承载该对象,然后将其传递到子视图中去:

struct SubView: View {let model: Modelvar body: some View {RoundedRectangle(cornerRadius: 15.0).fill(.red).frame(width: 150, height: 150).overlay {VStack {Text("\(model.value)")}}.onTapGesture {model.value += 1}.foregroundStyle(.white)}
}struct ContentView: View {@State var model = Model(0)@State var id = Int.random(in: 0...10000)var body: some View {NavigationStack {VStack {SubView(model: model)Button("刷新:\(id)") {id = Int.random(in: 0...10000)}}.padding().font(.largeTitle.weight(.black)).navigationTitle("@Observable 对象演示").toolbar {Text("大熊猫侯佩 @ CSDN").font(.headline.weight(.bold)).foregroundStyle(.gray)}}}
}

在上面的代码中,如果可以保证主视图(ContentView)本身不被重建,那么使用非状态来承载 model 对象也是可以的(但不推荐):

struct ContentView: View {let model = Model(0)var body: some View {NavigationStack {VStack {// 由于主视图的强稳定性,所以 SubView 对于 model 的引用也保持强稳定(即使是非状态)SubView(model: model)Button("刷新:\(id)") {id = Int.random(in: 0...10000)}}}}
}

当然,如果需要在子视图中也能更改 @Observable 对象本身,我们可以直接使用 @Bindable 来修饰它们。

现在,小伙伴们今后倘若在 SwiftUI 遇到类似的问题,相信也可以迎刃而解啦,棒棒哒!💯


更多 Swift 5.9 中新 Observation 框架知识的介绍,请小伙伴们移步如下链接观赏:

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

总结

在本篇博文中,我们讨论了在 SwiftUI 中融合 Swift 5.9 新 @Observable 对象的几种方式,并比较了它们细微差别下的潜在陷阱,最后提供了非常简单的解决之道。

感谢观赏,再会!😎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/688201.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++学习Day06之继承方式

目录 一、程序及输出1.1 公共继承1.1.1 父类中公共成员,子类可以正常访问1.1.2 父类中保护成员,子类类外不可以访问1.1.3 父类中私有成员,子类无法访问 1.2 保护继承1.2.1 父类中公共权限 子类中变为 保护权限1.2.2 父类中保护权限 子类中变为…

自定义Linux登录自动提示语

设置提示语的方式 在Linux系统中,可以通过修改几个特定的文件来实现在用户登录时自动弹出提示语。以下是几个常用的方法: 1. 修改/etc/issue文件: 这个文件用于显示本地登录前的提示信息 sudo vi /etc/issue在项目合作的时候,…

平衡二叉树(AVL树)

定义: 左右子树高度之差不超过1左右子树都是平衡二叉树 平衡二叉树的增删操作都离不开二叉树的调整 二叉树调整 LL型:右旋 LR型:左旋右旋 RR型:左旋 RL型:右旋左旋

FPFH特征描述符、对应关系可视化以及ICP配准

一、FPFH特征描述符可视化 C #include <pcl/point_types.h> #include <pcl/point_cloud.h> #include <pcl/search/kdtree.h> #include <pcl/io/pcd_io.h> #include <pcl/features/normal_3d_omp.h>//使用OMP需要添加的头文件 #include <boo…

批量追踪中通快递

在物流信息的管理中&#xff0c;批量追踪中通快递单号一直是个让人头疼的问题。但有了固乔快递查询助手&#xff0c;这一切都变得轻而易举。 固乔快递查询助手&#xff0c;作为市场上备受好评的快递查询软件&#xff0c;专门针对批量查询需求进行了优化。用户只需将中通快递单号…

【Linux】简单的网络计算器的实现(自定义协议,序列化,反序列化)

文章目录 前言一、 服务端1.ServerCal.cc&#xff08;服务器主文件&#xff09;2.ServerCal.hpp3.Sock.hpp(套接字封装)4.TcpServer.hpp(服务器)5.Protocol&#xff08;自定义协议&#xff09; 二、用户端1.ClientCal 三、Log.hpp&#xff08;日志&#xff09;四、makefile 前言…

【Qt学习】QIcon类 + 利用qrc机制设置图片路径(QtCreator)

文章目录 1. QIcon / windowIcon2. setIcon() 与 setwindowIcon()2.1 setIcon() 介绍与使用2.2 setWindowIcon 介绍与使用 3. 路径问题 & qrc机制的引入3.1 绝对路径 / 相对路径 的问题3.2 qrc机制3.3 在QtCreator下利用qrc机制引入图片 1. QIcon / windowIcon QIcon QIco…

单输入Doherty 功率放大器的极限带宽分析(含Matlab分析代码)

单输入Doherty 功率放大器的极限带宽分析&#xff08;含Matlab分析代码&#xff09; 参考论文&#xff1a;高效连续型射频功率放大器研究 DPA的基础理论参考&#xff1a;理想架构的Doherty功率放大器理论与仿真 1、单输入Doherty 功率放大器架构 假设 Doherty 功率放大器的…

介绍7款免费的最佳地图/导航/定位/GIS开源项目

文章目录 1、xdh-map新德汇地图应用类库1.1、独立引用1.2、与MyUI结合使用1.3、快速上手1.3.1、采用项目工程模板创建项目【推荐】1.3.2、 调用组件库功能 2、蚂蚁金服AntV-L7地理空间数据可视分析引擎2.1、AntV-L7简介2.2、核心特性2.3、支持丰富的图表类型2.4、如何使用2.4.1…

反转一个单链表

反转一个单链表 题意&#xff1a;反转一个单链表。 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL 思路 需要虚拟节点么&#xff1f; 答&#xff1a;不需要&#xff0c;因为没有删除节点&#xff0c;只是改变了节点的指向。 遍…

计算机设计大赛 深度学习二维码识别

文章目录 0 前言2 二维码基础概念2.1 二维码介绍2.2 QRCode2.3 QRCode 特点 3 机器视觉二维码识别技术3.1 二维码的识别流程3.2 二维码定位3.3 常用的扫描方法 4 深度学习二维码识别4.1 部分关键代码 5 测试结果6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天…

Rust-知多少?

文章目录 前言1.使用下划线开头忽略未使用的变量2. 变量解构3.常量4.变量遮蔽&#xff08;shadowing&#xff09;5. 类似println!("{}", x); 为啥加感叹号6.单元类型总结 前言 Rust 学习系列&#xff0c;记录一些rust使用小技巧 1.使用下划线开头忽略未使用的变量 …

MySQL跨服务器关联查询

1. 首先确认服务器的Federated引擎是否开启 show engines;修改数据库的配制文件my.ini,(我的my.ini的路径为&#xff1a;D:\ProgramData\MySQL\MySQL Server 5.7/my.ini),将federated添加到my.ini文件中 到MySQL的my.cnf配置文件中修改 在 [mysqld] 下方加入 federated 然后重…

通过前序和中序遍历结果构造二叉树

题目 105. 从前序与中序遍历序列构造二叉树 - 力扣&#xff08;LeetCode&#xff09; 思路 首先思考&#xff0c;根节点应该做什么。 肯定要想办法确定根节点的值&#xff0c;把根节点做出来&#xff0c;然后递归构造左右子树即可。 我们先来回顾一下&#xff0c;前序遍历和…

全网超全的测试类型详解,再也不怕面试答不出来了!

在软件测试工作过程中或者在面试过程中经常会被问到一些看起来简单但是总是有些回答不上的问题&#xff0c;比如你说说“黑盒测试和白盒测试的区别&#xff1f;”&#xff0c;“你们公司做灰度测试么&#xff1f;", ”α测试和β测试有什么不一样&#xff1f;“&#xff0…

review 10

整理磁盘操作的完整流程&#xff0c;如何接入虚拟机&#xff0c;是否成功识别&#xff0c;对磁盘分区工具的使用&#xff0c;格式化&#xff0c;挂载以及取消挂载、复习cp、mv和find指令 1&#xff1a;U盘接入虚拟机 在弹出窗口直接选择 虚拟机-可移动设备-找到u盘-连接 2&a…

matlab代码--基于注水法的MIMO信道容量实现

今天接触一个简单的注水法程序&#xff0c;搞懂数学原理即可看懂代码。 1 注水法简介 详细原理可以参考&#xff1a; MIMO的信道容量以及实现 大致理论就是利用拉格朗日乘子法&#xff0c;求解信道容量的最大化问题&#xff0c;得到的解形如往水池中注水的形式&#xff0c;最…

过字符设备驱动分步注册过程实现LED驱动的编写,编写应用程序测试,发布到CSDN

头文件 #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct{unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }gpio_t; #define PHY_LED1_ADDR 0X50006000 #define PHY_LED2_ADDR 0X50007000 #d…

信号系统之移动平均滤波器

1 通过卷积实现 移动平均滤波器的工作原理是平均输入信号中的多个点&#xff0c;以产生输出信号中的每个点。在方程式形式中&#xff0c;这样写&#xff1a; 其中是 x 输入信号&#xff0c;y 是输出信号&#xff0c;M 是平均值中的点数。例如&#xff0c;在 5 点移动平均滤波器…

基于51单片机的智能台灯的设计与实现

摘 要:针对青少年因坐姿不正确、灯光亮度不合适、用眼过度等原因易导致的近视问题,文中提出使用51单片机作为主控制单元,选用红外检测、光敏检测、蓝牙通信、蜂鸣器和模数转换等模块,设计了一款智能台灯。该智能台灯具有节能、预防近视等功能。经测试,该台灯具有保护视力的…