如何管理 UI 状态?可行方案3选1,AI 别乱写
- 只有 View @State 持有状态,通过 UI 回调或者查询一个无状态的 actor 来更新
- 当且仅当状态本身与逻辑无关,仅负责显示。如文件名列表
- 只有 Observable 持有状态,View 直接持有 Observable 并访问其中的
- 当且仅当某个 UI 状态是轻量后端逻辑的主动触发者。如页面切换管理者需要在切换页面的同时暂停 ARSession,想要将两者绑定管理
- actor 持有真实状态,对 actor 注入一个 Observable,Observable 持有真实状态的拷贝,UI 引用该 Observable
- 当且仅当某个 UI 状态是重量后端逻辑的主动触发者。如录制器的已开始、结束中、取消中,想要和真实的录制逻辑绑定管理,此时多个 await 会有严重代价
- 或:某个 UI 状态是后端自发变化状态的映射。如网络管理器自发变化时需要 UI 显示已连接设备
- 需要测试的是,在其他 actor 执行 await MainActor 是否有 16.7ms 代价
注意
- View 用 @State 表示管理生命周期。如果只需要观测,则什么宏都不用.
- View 用 @Bindable 表示引用 + 非管理 + 需要 self.$xxx
对于 1 的例子
1struct UploadView: View {2 @State var list: []3 @State var text: String4 let finder: UploadFinder // 假设这是 actor5
6 init(finder) { self.finder = finder } // 注入7
8 body {9 Button { self.text = "clicked" } // button 回调10 }11 .onAppear {12 self.list = await self.finder.findAllFiles() // 无状态的查询13 }14}对于 2 的例子
1struct UploadView: View {2 @ObservedObject var page: PageCoodinator3
4 init(page) { self.page = page }5
6 body {7 switch self.page.currentPage { case ... SomeView() }8 Button { self.page.changePage() }9 }10 .onAppear {11 }12}13
14@Observable15class PageCoodinator {10 collapsed lines
16 var currentPage: Int17 let session = ARSession()18
19 init() { self.session.run() }20
21 func changePage() {22 self.currentPage += 1 // ui 状态和 session 绑定,逻辑轻23 self.session.pause()24 }25}对于 3 的例子
1@Observable2class RecorderViewModel {3 var state: String // 状态的拷贝4}5actor Recorder {6 var state: String = "idle"7 let viewModel: RecorderViewModel8
9 init(viewModel) { self.viewModel = viewModel } // 注入 viewModel10
11 func run() {12 await something.prepare() // 逻辑重13 await something.prepare()14 self.state = "running" // 后端状态15 await MainActor.run { self.viewModel.state = "running" } // ui 状态和后端状态可以在一块儿管理26 collapsed lines
16 }17}18
19struct RecorderView: View {20 var viewModel: RecorderViewModel21 let recorder: Recorder22
23 init(recorder, viewModel) { // 注入24 self.recorder = recorder25 self.viewModel = viewModel26 }27
28 body {29 await self.recorder.run()30 }31}32
33@Observable34class PageCoodinator {35 let recorder: Recorder36 let viewModel: RecorderViewModel37 init() {38 self.viewModel = RecorderViewModel()39 self.recorder = Recorder(self.viewModel)40 }41}另外一个(自发状态变化)的例子:
1@Observable2class NetworkViewModel {3 var connected: Int = 04}5
6actor Network {7 var connected: Int8 let viewModel: NetworkViewModel9
10 init(viewModel) { self.viewModel = viewModel; self.loop() }11
12 private func loop() {13 if ("new device found") {14 self.connected += 115 self.viewModel.connected += 13 collapsed lines
16 }17 }18}