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


    在这里插入图片描述

    0. 概览

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

    在这里插入图片描述

    Observation 框架可以增强通用场景下的使用,也可以搭配 SwiftUI 5.0 而获得双剑合璧的更强威力。

    那么,就让我们赶快进入 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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    如上代码所示,我们定义了两个可观察类 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    以上代码输出如下:

    在这里插入图片描述

    使用 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)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    注意,上面代码中 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 {...}
    
    • 1
    • 2

    有了新 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)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    现在,即使跨越多重层级关系我们也可以只通过 @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
                }))
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    不过,对于之前 @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()
                } 
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    运行效果如下:

    在这里插入图片描述

    “临时可变”这一技术可以用于视图中任何化“不变”为“可变”的场景中,当然对于直接视图间对象的传递,我们可以使用 @Bindable 这一更为“正统”的方法。

    6. 总结

    在本篇博文中,我们讨论了在 Swift 5.0 和 SwiftUI 5.0 中大放异彩 Observation 框架的使用,并就诸多技术细节问题给与了详细的介绍,愿君喜欢。

    感谢观赏,再会!😎

  • 相关阅读:
    Linux环境升级npm和node的版本
    Beaustiful Soup爬虫案例
    HTML5 WebSocket:实时通信的新篇章
    基于WEB的网上购物系统的设计与实现
    Android编译系统apk并进行系统签名安装
    python进阶(28)import导入机制原理
    Ubuntu16.04系统在Docker容器搭建 Gitlab 服务器
    uni-app点击按钮弹出提示框-uni.showModal(OBJECT),选择确定和取消
    Vulnhub系列靶机---Deathnote: 1死亡笔记
    效率提升一键完成房产小程序源码揭秘高效业务流程优化
  • 原文地址:https://blog.csdn.net/mydo/article/details/133543655