非 SwiftUI View 类型如何观察 @Observable 所标记类型的值的变化?

| Swift , iOS

想第一时间获取对于自己有帮助的新内容? 欢迎关注Ficow的公众号:
学良学不停-compact.png

 

内容概览

  • 前言
  • 示例项目
  • 运行过程解析
  • (加餐)解析 Observable 宏的内部实现
  • 总结

 

前言

 

Observation 框架用于自动跟踪应用程序数据模型状态的变化。它通过属性包装器和可观察对象来检测和响应状态变化,从而简化状态管理。

使用Observation框架可以减少大量的样板代码,提高应用响应速度,并简化数据模型的管理。

这使得开发者能够更容易地保持应用中不同部分的数据同步,并确保UI能够实时反映数据的变化。

但是,我们如何在非 SwiftUI View 类型中观察被 @Observable 标记的类型的值变化呢?

接下来,和 Ficow 一起探索一下吧~

 

示例项目

 

为了便于读者朋友理解,请克隆这个示例项目的代码 ObservationDemo,并运行预览以体验实际的运行效果。

首先,在示例项目的 ContentView.swift 中启动预览:

模拟器展示的UI内容,分别如下:

第一行是静态文本,第二行是一个文本输入框,第三行是监听代码输出的日志文本。

在启动预览后 1 秒,UI 会自动刷新:

接下来,Ficow 和你一起解析示例代码的运行过程。

 

运行过程解析

 

一图胜前言,客官请看 Ficow 这个简单的时序图:

其实,这里最核心的代码就是:

AsyncStream.observeOnMainActor {
    self.viewState.text
} newValue: { text in
    // Note: this can also get the initial value
    self.viewState.log = "Detect new text: \(text)"
}

它利用 AsyncStream 完成了对 Observation.withObservationTracking 方法的封装,如此便可持续地观察被 @Observable 宏修饰了的类型其内部字段的变更。

因为 withObservationTracking 方法只观察一次值的变化,所以这里的内部实现看起来是在执行递归调用:

@Sendable func observe() {
    let change = Observation.withObservationTracking {
        apply()
    } onChange: {
        queue.async { // 这里的异步执行很有必要,它可以确保上一次观察的回调可以先被执行,然后再进行下一次观察
            observe() // 递归调用,进行下一次观察
        }
    }
    continuation.yield(change)
}

这个递归调用,只会在被观察的值发生了变化的时候,才会被执行。所以,这个方法本身并不会导致死循环。

 

(加餐) 解析 Observable 宏的内部实现

 

首先,找到 RootViewState 这个类型,然后点击 @Observable,并选择展开宏(Expand Macro):

接下来,你会看到这个宏生成的具体代码(注意观察 accesswithMutation 方法):

然后,以同样的方式继续展开 ObservationTracked 宏:

这时候,你就看到了 text 属性是如何被封装的:

结合前面 Observable 宏展开的结果,我们也就理解了这些宏的主要目标就是:

包装 Observable 宏标记类型中属性的 getter, setter,以实现值的监听

 

总结

 

SwiftUI 已经从一个不成熟的“试验品”逐渐成长为了一个完善的 UI 框架。

然而,开发者们还是要面对旧版本的 SwiftUI。

新旧技术混用是在所难免的,我们开发者能做的就是尽可能地向后兼容,逐步地用新技术去替换掉旧的技术~

 

参考内容:
Observation
深入理解 Observation - 原理,back porting 和性能

 

觉得不错?点个赞呗~

本文链接:非 SwiftUI View 类型如何观察 @Observable 所标记类型的值的变化?

转载声明:本站文章如无特别说明,皆为原创。转载请注明:Ficow Shen's Blog

评论区(期待你的留言)