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

news2024/9/19 11:13:49

在这里插入图片描述

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: String
    var power: Int
    
    init(name: String, power: Int) {
        self.name = name
        self.power = power
    }
}

@Observable
final class Model {
    var title: String
    var createAt: Date?
    var heros: [Hero]
    
    init(title: String, heros: [Hero]) {
        self.title = title
        self.createAt = Date.now
        self.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. 它默认只会被调用 1 次,所以上面为了能够重复监听,我们在 onChange 闭包里对 watching() 方法再次进行了调用;
  2. withObservationTracking() 方法的 apply 闭包不管如何都会被调用 1 次,即使其监听的属性从未改变过;
  3. 在监听闭包中只能得到属性改变前的旧值;

目前,上面测试代码在 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 model
    
    var body: some View {
        List(model.heros) { hero in
            HStack {
                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: Hero
    
    var 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 model
    
    var body: some View {
        // 在 body 内将 model 改为可变
        @Bindable var model = model
        
        VStack {
            List(model.heros) { hero in
                HStack {
                    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 框架的使用,并就诸多技术细节问题给与了详细的介绍,愿君喜欢。

感谢观赏,再会!😎

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

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

相关文章

计算机网络笔记3 数据链路层

计算机网络系列笔记目录&#x1f447; 计算机网络笔记6 应用层计算机网络笔记5 运输层计算机网络笔记4 网络层计算机网络笔记3 数据链路层计算机网络笔记2 物理层计算机网络笔记1 概述 文章前言 &#x1f497; 站在巨人的肩膀上&#xff0c;让知识的获得更加容易&#xff01…

给列起别名(关键字:as)

MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 语法格式: select 列名1 as 别名1, 列名2 as 别名2, 列名n as 别名n from 表名; 说明&#xff1a;可以省略as&#xff0c;列名和别名之间使用空格…

力扣 -- 873. 最长的斐波那契子序列的长度

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int lenLongestFibSubseq(vector<int>& nums) {int nnums.size();unordered_map<int,int> hash;for(int i0;i<n;i){hash[nums[i]]i;}int ret2;vector<vector<int>> dp(n,v…

关于内存对齐你需要了解的事

简介 这篇文章的目的是带你了解什么是内存对齐&#xff0c;具体的内存对齐的细节、处理方式、不同架构则不会去详细讲解&#xff0c;只做科普文用。 1、什么是内存对齐 内存对齐和数据在内存中的位置有关。内存对齐以字节为单位进行&#xff0c;一个变量的内存地址如果正好等于…

集合(容器)-List接口及实现类

容器的特征&#xff1a;①数据长度可变&#xff1b;②数据保存方式不同。 集合体系概述&#xff1a;JAVA的集合框架是由很多接口、抽象类、具体类组成。都位于java.util包中。 Java中集合类中默认可以存储任意数据类型&#xff0c;Java中的集合提供泛型机制&#xff0c;在定义…

mysql5.7停止维护时间

mysql5.7将于2023年10月停止官网支持和更新&#xff1b;老项目要准备升级&#xff0c;新项目的mysql必须是mysql8.0&#xff08;2023-10&#xff09; 官方升级咨询地址 oracle官方升级咨询地址https://go.oracle.com/LP116153?elq_mid247718&sh1518132002061316121320310…

.Net开源迁移框架FluentMigrator的使用。

在实际的开发过程中&#xff0c;经常会遇到数据库结构变动&#xff0c;比如新增表、删除表&#xff1b;已有的表新增字段&#xff0c;删除字段&#xff1b;修改字段属性等等。而且需要开发环境、测试环境和生产环境进行同步。如果使用的是EF&#xff0c;还是挺方便的。而非EF环…

Android笔记:Android 组件化方案探索与思考

组件化项目&#xff0c;通过gradle脚本&#xff0c;实现module在编译期隔离&#xff0c;运行期按需加载&#xff0c;实现组件间解耦&#xff0c;高效单独调试。 先来一张效果图 组件化初衷 APP版本不断的迭代&#xff0c;新功能的不断增加&#xff0c;业务也会变的越来越复杂…

【多模态融合】TransFusion学习笔记(2)

接上篇【多模态融合】TransFusion学习笔记(1)。 从TransFusion-L到TransFusion ok,终于可以给出论文中那个完整的框架图了&#xff0c;我第一眼看到这个图有几个疑问: Q&#xff1a;Image Guidance这条虚线引出的Query Initialization是什么意思? Q&#xff1a;图像分支中的…

flex布局与几个实例(含源码)

本文简单的说明下flex布局 有源码实例&#xff0c;后续会持续添加 flex默认主轴是横轴 容器主要有6个属性 flex-direction 决定主轴的方向 flex-direction: row | row-reverse | column | column-reverse; flex-wrap 决定是否换行 flex-wrap: nowrap | wrap | wrap-revers…

JavaAPI---replace

package daysreplace;public class ReplaceTest {public static void main(String[] args) {String str "wwxhhhhhhhhhhh333";System.out.println("替换前的字符串" str);String newstr str.replace("333", "111");System.out.prin…

(第2遍中)内存的堆空间不够 error: MSB3073 超过了 PCH 的虚拟内存范围

压缩包里打开的新工程文件&#xff0c;运行 GenerateProjectFiles.bat 后&#xff0c;再点击 .sln 文件&#xff0c;编译工程&#xff0c;编译了 1.5h 左右&#xff0c;快结束的时候报错如下&#xff1a; 编译器的堆空间不足在第2遍中编译器的堆空间不足error MSB3073: 命令“…

【typescript】面向对象(下篇),包含接口,属性的封装,泛型

假期第八篇&#xff0c;对于基础的知识点&#xff0c;我感觉自己还是很薄弱的。 趁着假期&#xff0c;再去复习一遍 面向对象&#xff1a;程序中所有的操作都需要通过对象来完成 计算机程序的本质就是对现实事物的抽象&#xff0c;抽象的反义词是具体。比如照片是对一个具体的…

大恒IFrameData IImageData转bmp HObject Mat

大恒工业相机采集的帧数据转为其他8bit图像格式 C#转为bmp格式转为Halcon的HObject格式转为OpenCVSharp的Mat格式 回调采集图像的数据类型为IFrameData&#xff0c;单帧采集的数据类型为IImageData&#xff0c;两者的区别为IImageData类多了一个**Destroy()**方法 C# 转为bm…

floyd算法细节

这个不是一篇学习性文章 主要是针对这几天思考的问题进行一些回答 floyD在计网和数据结构和图模型中有广泛的应用算法 很简单但是其中蕴含的原理值得细究。 弗洛伊德算法(Floyd)主要针对多源最短路径,且可以解决路径中有负权的情况(不包含负权回路),但是迪杰斯特拉算法只…

mycat实现mysql读写分离

架构图&#xff1a; 视频地址

逐步解决Could not find artifact com:ojdbc8:jar:12

Could not find artifact com:ojdbc8:jar:12 in central (https://repo.maven.apache.org/maven2) 原因&#xff1a; ojdbc8:jar:12 属于Oracle 数据库链接的一个程序集&#xff0c;缺失的话很有可能会影响数据库链接&#xff0c;蝴蝶效应产生不可预测的BUG&#xff01;但是版…

typescript开发环境搭建

typescript是基于javascript的强类型标记性语言&#xff0c;使用typescript语言可开发出不同规模的、易于扩展的web前端页面应用&#xff0c;本文主要描述typescript的开发环境搭建。 npm install -g typescript 如上所示&#xff0c;在本地开发环境中&#xff0c;使用nodejs…

arduino嵌入式1,LED闪烁案例

CVE系列在等等吧&#xff0c;环境我有点懒得搭建了 文章目录 前言一、anduino是什么玩意儿&#xff1f;二、使用步骤1.找蓝图/画蓝图2.写入数据成果 总结 前言 最近在学习嵌入式开发&#xff0c;我的单片机到了&#xff0c;然后我就沉迷于嵌入式开发的环境中 提示&#xff1a;…

1300*B. Sort the Array(排序构造)

题意&#xff1a; 一个无重复数字的序列&#xff0c;能否反转一个区间使其成为单增序列。 解析&#xff1a; 记录每个点的值和初始下标&#xff0c;然后排序。 对于排好序的序列进行遍历&#xff0c;找出第一段和原序列下标不同的区间并且标记&#xff08;此区间即为翻转的区…