SwiftUI 在批处理文件上传期间显示刷新进度

问题描述

上下文(macOS 应用程序): 用户可以选择一堆图像,然后,我开始一次同时上传至少 5 个文件。问题是我想显示从服务器收到的每个图像的进度。我对该数组使用 @State 指标来观察变化。 进度状态可以是:待定、进行中 (%) 和完成。 我需要为每个图像显示 %。

问题: 进度更新非常频繁,我需要在 ScrollView 中反映这些更改。 图像的布局显示为网格视图(方形显示),但这会导致 3 个问题:

  1. var body: some View { ... } 大约每秒被调用 2 - 3 次
  2. 滚动视图在所有这些刷新过程中都卡住
  3. 如此多的视图刷新也会导致内存飙升至 1GB 上传完成后,内存恢复到正常水平(50 - 70mb)

我什至尝试使用计时器更新视图,每秒仅反映一次更改,但这并没有太大帮助。

为了改进渲染,我为每个网格视图和滚动视图添加.id(),我可以看到对资源使用的小幅改进,但对用户来说感觉还不够好。

我的问题很简单,使用 SwiftUI (1.0) 的最佳实践是什么 - 我们还需要支持 macOS 10.15,而不仅仅是 macOS 11 - 以实现这种 UX,您可以在其中看到上传文件的进度但您仍然可以滚动查看待处理的文件或已上传文件

解决方法

由于您没有提供任何代码,因此很难真正深入挖掘,但这里有一些需要考虑的事项:

  1. 调用 var body: some View { ... } about 2 - 3 times per second 本身不应成为瓶颈。我有 SwiftUI 代码,它以每秒 60 次刷新的速度运行而不会出现问题。但是,它表明 body 调用中的某些东西很昂贵。 (没有代码无法分辨)

  2. 尽可能使用 Equatable -- 特别是对于昂贵的视图。这将确保在不需要时不会重新渲染昂贵的视图。如果您只拥有昂贵的视图的一部分,那么值得将其重构为自己的视图以成为 Equatable。更多阅读:https://www.hackingwithswift.com/example-code/language/how-to-conform-to-the-equatable-protocol

  3. 您的数据模型可以重构为带有 ObservableObjectStateObjectPublisher,您可以在其中使用 Combine 做一些聪明的事情来减慢更新速度。首先想到的是使用 throttle 来确保您发布的更新仅以 X 间隔反馈到您的视图中。更多阅读:https://rhonabwy.com/2019/12/15/combine-throttle-and-debounce/

  4. 确保您的上传工作在后台线程上完成,并且您的 UI 更新在主线程上进行。根据您上传的方式,Combine 可能会再次帮助您,让您在更新进来时按顺序回调主线程(也可以不使用 Combine,但 Combine 使事情变得非常简单)。更多阅读:http://trycombine.com/posts/subscribe-on-receive-on/

  5. 打开 Instruments,看看是什么造成了您所看到的所有内存使用情况。这应该会提示您分配发生的位置以及优化的位置。

最后,SwiftUI List 在 Catalina 上的性能充其量是值得怀疑的。大苏尔更好。在 SwiftUI 2.0 中,您可以更多地依赖于 LazyVStack 之类的东西,但这不是定位 Catalina 的解决方案。如果你真的很绝望,你可以回到 AppKit 并在那里列出你的清单,把它包装在 NSViewRepresentable