当前位置: 首页 > news >正文

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 对象的几种方式,并比较了它们细微差别下的潜在陷阱,最后提供了非常简单的解决之道。

感谢观赏,再会!😎

相关文章:

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

概览 在 Swift 5.9 中,苹果为我们带来了全新的可观察框架 Observation,它是观察者开发模式在 Swift 中的一个全新实现。 除了自身本领过硬以外,Observation 框架和 SwiftUI 搭配起来也能相得益彰,事倍功半。不过 Observable 对象…...

分享一个学英语的网站

名字叫:公益大米网​​​​​​​ Freerice 这个网站是以做题的形式来记忆单词,题干是一个单词,给出4个选项,需要选出其中最接近题干单词的选项。 答对可以获得10粒大米,网站的创办者负责捐赠。如图 触发某些条件&a…...

【动态规划】【C++算法】2742. 给墙壁刷油漆

作者推荐 【数位dp】【动态规划】【状态压缩】【推荐】1012. 至少有 1 位重复的数字 本文涉及知识点 动态规划汇总 LeetCode2742. 给墙壁刷油漆 给你两个长度为 n 下标从 0 开始的整数数组 cost 和 time ,分别表示给 n 堵不同的墙刷油漆需要的开销和时间。你有…...

【后端高频面试题--设计模式上篇】

🚀 作者 :“码上有前” 🚀 文章简介 :后端高频面试题 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 往期精彩内容 【后端高频面试题–设计模式上篇】 【后端高频面试题–设计模式下篇】 【后端高频…...

P3141 [USACO16FEB] Fenced In P题解

题目 如果此题数据要小一点,那么我们可以用克鲁斯卡尔算法通过,但是这个数据太大了,空间会爆炸,时间也会爆炸。 我们发现,如果用 MST 做,那么很多边的边权都一样,我们可以整行整列地删除。 我…...

Android Compose 一个音视频APP——Magic Music Player

Magic Music APP Magic Music APP Magic Music APP概述效果预览-视频资源功能预览Library歌曲播放效果预览歌曲播放依赖注入设置播放源播放进度上一首&下一首UI响应 歌词歌词解析解析成行逐行解析 视频播放AndroidView引入Exoplayer自定义Exoplayer样式横竖屏切换 歌曲多任…...

Nginx实战:安装搭建

目录 前言 一、yum安装 二、编译安装 1.下载安装包 2.解压 3.生成makefile文件 4.编译 5.安装执行 6.执行命令软连接 7.Nginx命令 前言 nginx的安装有两种方式: 1、yum安装:安装快速,但是无法在安装的时候带上想要的第三方包 2、…...

Qt之条件变量QWaitCondition详解(从使用到原理分析全)

QWaitCondition内部实现结构图: 相关系列文章 C之Pimpl惯用法 目录 1.简介 2.示例 2.1.全局配置 2.2.生产者Producer 2.3.消费者Consumer 2.4.测试例子 3.原理分析 3.1.源码介绍 3.2.辅助函数CreateEvent 3.3.辅助函数WaitForSingleObject 3.4.QWaitCo…...

OpenSource - 一站式自动化运维及自动化部署平台

文章目录 orion-ops 是什么重构特性快速开始技术栈功能预览添砖加瓦License orion-ops 是什么 orion-ops 一站式自动化运维及自动化部署平台, 使用多环境的概念, 提供了机器管理、机器监控报警、Web终端、WebSftp、机器批量执行、机器批量上传、在线查看日志、定时调度任务、应…...

【后端高频面试题--设计模式下篇】

🚀 作者 :“码上有前” 🚀 文章简介 :后端高频面试题 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 后端高频面试题--设计模式下篇 往期精彩内容设计模式总览模板方法模式怎么理解模板方法模式模板方…...

这才是大学生该做的副业,别再痴迷于游戏了!

感谢大家一直以来的支持和关注,尤其是在我的上一个公众号被关闭后,仍然选择跟随我的老粉丝们,你们的支持是我继续前行的动力。为了回馈大家长期以来的陪伴,我决定分享一些实用的干货,这些都是我亲身实践并且取得成功的…...

Ubuntu20.04 安装jekyll

首先使根据官方文档安装:Jekyll on Ubuntu | Jekyll • Simple, blog-aware, static sites 如果没有报错,就不用再继续看下去了。 我这边在执行gem install jekyll bundler时报错,所以安装了rvm,安装rvm可以参考这篇文章Ubuntu …...

AWK语言

一. awk awk:报告生成器,格式化输出。 在 Linux/UNIX 系统中,awk 是一个功能强大的编辑工具,逐行读取输入文本,默认以空格或tab键作为分隔符作为分隔,并按模式或者条件执行编辑命令。而awk比较倾向于将一行…...

精通Nmap:网络扫描与安全的终极武器

一、引言 Nmap,即NetworkMapper,是一款开源的网络探测和安全审计工具。它能帮助您发现网络中的设备,并识别潜在的安全风险。在这个教程中,我们将一步步引导您如何有效地使用Nmap,让您的网络更加安全。 因为Nmap还有图…...

Java 学习和实践笔记(11)

三大神器&#xff1a; 官方网址: http://www.jetbrains.com/idea/ 官方网址: https://code.visualstudio.com/ 官方网址: http://www.eclipse.org 装好了idea社区版&#xff0c;并试运行以下代码&#xff0c;OK&#xff01; //TIP To <b>Run</b> code, press &l…...

开发实体类

开发实体类之间先在pom文件中加入该依赖 <!-- 开发实体类--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency>我们在实体类中声明各个属…...

人工智能学习与实训笔记(十五):Scikit-learn库的基础与使用

人工智能专栏文章汇总&#xff1a;人工智能学习专栏文章汇总-CSDN博客 本篇目录 一、介绍 1. 1 Scikit-learn的发展历程及定义 1.2 理解算法包、算法库及算法框架之间的区别和联系 二、Scikit-learn官网结构 三、安装与设置 3.1 Python环境的安装与配置 3.2 Scikit-lea…...

插值与拟合算法介绍

在数据处理和科学计算领域,插值与拟合是两种极为重要的数据分析方法。它们被广泛应用于信号处理、图像处理、机器学习、金融分析等多个领域,对于理解和预测数据趋势具有至关重要的作用。本文将深入浅出地介绍这两种算法的基本原理,并结合C语言编程环境探讨如何在CSDN开发者社…...

下一代Windows系统曝光:基于GPT-4V,Agent跨应用调度,代号UFO

下一代Windows操作系统提前曝光了&#xff1f;&#xff1f; 微软首个为Windows而设的智能体&#xff08;Agent&#xff09; 亮相&#xff1a; 基于GPT-4V&#xff0c;一句话就可以在多个应用中无缝切换&#xff0c;完成复杂任务。整个过程无需人为干预&#xff0c;其执行成功…...

二.自定义头文件

一.Worker.h 1.1概述 - 类名&#xff1a;Worker - 继承关系&#xff1a;所有其他类&#xff08;Employee、Manager、Boss&#xff09;都继承自该抽象类 - 头文件保护&#xff1a;使用 pragma once 防止头文件重复包含 - 引入标准库&#xff1a;包含 <iostream> 和 <st…...

废品买卖回收管理系统(10053)

有需要的同学&#xff0c;源代码和配套文档领取&#xff0c;加文章最下方的名片哦 一、项目演示 项目演示视频 二、资料介绍 完整源代码&#xff08;前后端源代码SQL脚本&#xff09;配套文档&#xff08;LWPPT开题报告/任务书&#xff09;远程调试控屏包运行一键启动项目&…...

基于Ollama与Streamlit的本地大模型智能对话应用snowChat部署指南

1. 项目概述&#xff1a;一个基于本地大模型的智能对话应用最近在折腾本地部署的大语言模型&#xff0c;发现了一个挺有意思的项目&#xff0c;叫snowChat。这名字听起来就挺“冷”的&#xff0c;但功能却很“热”——它本质上是一个让你能在自己电脑上&#xff0c;用本地的大模…...

如何用opendbc解决汽车CAN总线解码难题:一份完整的实践指南

如何用opendbc解决汽车CAN总线解码难题&#xff1a;一份完整的实践指南 【免费下载链接】opendbc a Python API for your car 项目地址: https://gitcode.com/gh_mirrors/op/opendbc 面对现代汽车复杂的电子控制系统&#xff0c;你是否曾经困惑于如何理解车辆内部的数据…...

ANFIS驱动的电力系统稳定控制器方法【附代码】

✨ 长期致力于电力系统稳定性、PSS2A、ANFIS研究工作&#xff0c;擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流&#xff0c;点击《获取方式》 &#xff08;1&#xff09;基于减法聚类与混合学习的ANFIS结构自动生成方法&#xf…...

DLSS Swapper终极指南:一键管理游戏图形增强文件,释放显卡全部性能

DLSS Swapper终极指南&#xff1a;一键管理游戏图形增强文件&#xff0c;释放显卡全部性能 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper DLSS Swapper是一款专为游戏玩家设计的智能图形增强文件管理工具&#xff0c;…...

TouchGFX SPI屏移植避坑全记录:从下载算法到分散加载.sct文件

TouchGFX SPI屏移植实战&#xff1a;破解下载算法与分散加载的三大技术难点 当一块240x320的SPI接口屏幕在STM32F412RET6上流畅渲染出60帧的TouchGFX界面时&#xff0c;我盯着示波器上稳定的时序信号长舒一口气——这已经是本周第三次重写W25Q64的下载算法。与官方文档描述的&…...

WinFlexBison深度解析:Windows平台编译工具链的完整解决方案

WinFlexBison深度解析&#xff1a;Windows平台编译工具链的完整解决方案 【免费下载链接】winflexbison Main winflexbision repository 项目地址: https://gitcode.com/gh_mirrors/wi/winflexbison 在Windows平台上开发编译器、解释器或复杂文本解析器时&#xff0c;开…...

长期使用Taotoken聚合服务在模型路由与容灾方面的实际体感

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 长期使用Taotoken聚合服务在模型路由与容灾方面的实际体感 在持续数月的项目开发过程中&#xff0c;我们团队将多个AI模型调用统一…...

MATLAB找峰值进阶:用findpeaks函数5个鲜为人知的技巧,让你的科研图表更专业

MATLAB找峰值进阶&#xff1a;用findpeaks函数5个鲜为人知的技巧&#xff0c;让你的科研图表更专业 在科研数据分析中&#xff0c;峰值检测是最基础却又最关键的步骤之一。无论是光谱分析、色谱检测还是振动信号处理&#xff0c;准确识别和量化峰值特征直接影响着研究结论的可信…...

联想拯救者工具箱:让游戏本性能释放更自由的开源神器

联想拯救者工具箱&#xff1a;让游戏本性能释放更自由的开源神器 【免费下载链接】LenovoLegionToolkit Lightweight Lenovo Vantage and Hotkeys replacement for Lenovo Legion laptops. 项目地址: https://gitcode.com/gh_mirrors/le/LenovoLegionToolkit 联想拯救者…...