想第一时间获取对于自己有帮助的新内容? 欢迎关注Ficow的公众号:
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)
}
这个递归调用,只会在被观察的值发生了变化的时候,才会被执行。所以,这个方法本身并不会导致死循环。
首先,找到 RootViewState
这个类型,然后点击 @Observable,并选择展开宏(Expand Macro):
接下来,你会看到这个宏生成的具体代码(注意观察 access
和 withMutation
方法):
然后,以同样的方式继续展开 ObservationTracked
宏:
这时候,你就看到了 text
属性是如何被封装的:
结合前面 Observable
宏展开的结果,我们也就理解了这些宏的主要目标就是:
包装 Observable 宏标记类型中属性的 getter, setter,以实现值的监听
SwiftUI 已经从一个不成熟的“试验品”逐渐成长为了一个完善的 UI 框架。
然而,开发者们还是要面对旧版本的 SwiftUI。
新旧技术混用是在所难免的,我们开发者能做的就是尽可能地向后兼容,逐步地用新技术去替换掉旧的技术~
参考内容:
Observation
深入理解 Observation - 原理,back porting 和性能
觉得不错?点个赞呗~
本文链接:非 SwiftUI View 类型如何观察 @Observable 所标记类型的值的变化?
转载声明:本站文章如无特别说明,皆为原创。转载请注明:Ficow Shen's Blog