在 Swift 中,如何从异步任务外壳内部更新主线程上的文本视图
Posted
技术标签:
【中文标题】在 Swift 中,如何从异步任务外壳内部更新主线程上的文本视图【英文标题】:In Swift, how to update a Text view on main thread from inside async Task enclosure 【发布时间】:2022-01-21 03:03:42 【问题描述】:这是目标。我想运行一个长的异步任务,并让它定期向 UI 发送消息,这些消息将显示在旋转轮活动指示器的下方。现在,我不关心活动指示器。我只需要异步任务在其进程的各个阶段发送消息,并在消息进入时向用户显示该消息。
因此,我查看了 GCD 解决方案、可观察对象、演员、委托等。提供的许多解决方案都非常陈旧且无法正常工作,或者它们足够新,但太复杂了,我不理解他们,我对 Swift 还很陌生。
我确实找到了一种工作方法,但它太俗气了,它确实必须是错误的方法。我刚刚为我的主视图创建了一个扩展,这样我就可以将异步函数放在一个单独的文件中(它有点大)。所以我在 UI 中使用的字符串 var 对异步任务是可见的。但只有当我发生“尝试等待”时,它才会真正更新。这必须触发 UI 中的某些内容来刷新字符串并重新显示布局。但这似乎很愚蠢。一定有更好的办法。
无论如何,这是我的解决方法。顺便说一句,这几乎是整个项目,致力于解决这个问题。我已经删除了所有我无法工作的失败替代方案。另外,请原谅任务睡眠扩展。我宁愿在几秒钟内工作而不是纳秒。
import SwiftUI
struct ContentView: View
@State var progressmsg: String = ""
@State var loadingViewShowing = false
var body: some View
VStack
Button("Download Something")
loadingViewShowing = true
Task
print("try 1")
progressmsg = "doing first sleep task"
try await Task.sleep(seconds: 5)
print("try 2")
await diggingEvenDeeper()
print("try 3")
progressmsg = "doing third sleep task"
try await Task.sleep(seconds: 5)
loadingViewShowing = false
if !loadingViewShowing
Text("Nothing Happening ...")
else
ProgressView().scaleEffect(1.0, anchor: .center)
Text("\(progressmsg)").font(.title).fontWeight(.semibold)
.frame(width: 400, height: 400)
extension ContentView
func diggingEvenDeeper() async
//really long process, need a message on stages of progress
print("trying to start")
self.progressmsg = "doing second & really long sleep task"
do
try await Task.sleep(seconds: 5)
catch
print(error)
extension Task where Success == Never, Failure == Never
static func sleep(seconds: Double) async throws
let duration = UInt64(seconds * 1_000_000_000)
try await Task.sleep(nanoseconds: duration)
【问题讨论】:
符合@ObservableObject
的视图模型类比在视图中执行所有控制器 的东西要好得多。
【参考方案1】:
好的,我得到了这个工作。谢谢@vadian 的建议。我又看了一眼@ObservableObject,发现了一些我之前错过的细节。基本上,通过使用 @StateObject 来关注我的 observable 类(结构不起作用),它为我的后台线程提供了一个稳定的位置来引用(如果我说的没错的话)。
我还要指出,在我的实际项目中,我收到了关于不允许从后台线程更新已发布项目的紫色错误消息,因此我还必须将进度消息包装在 DispatchQueue.main.async 中外壳。奇怪的小示例程序没有抱怨。哦,好吧。
无论如何,这是可行的解决方案:
import SwiftUI
class Progress: ObservableObject
@Published var message = "Doing something ..."
@Published var value = 0.0
struct ContentView: View
@StateObject var progress: Progress = Progress()
//@State var progressmsg: String = ""
@State var loadingViewShowing = false
var body: some View
VStack
Button("Download Something")
loadingViewShowing = true
Task
print("try 1")
progress.message = "doing first sleep task"
try await Task.sleep(seconds: 5)
print("try 2")
await diggingEvenDeeper(progress: progress)
print("try 3")
progress.message = "doing third sleep task"
try await Task.sleep(seconds: 5)
loadingViewShowing = false
if !loadingViewShowing
Text("Nothing Happening ...")
else
ProgressView().scaleEffect(1.0, anchor: .center)
Text("\(progress.message)").font(.title).fontWeight(.semibold)
.frame(width: 400, height: 400)
//extension ContentView
func diggingEvenDeeper(progress: Progress) async
//really long process, need a message on stages of progress
print("trying to start")
progress.message = "doing second & really long sleep task"
do
try await Task.sleep(seconds: 5)
catch
print(error)
//
extension Task where Success == Never, Failure == Never
static func sleep(seconds: Double) async throws
let duration = UInt64(seconds * 1_000_000_000)
try await Task.sleep(nanoseconds: duration)
【讨论】:
以上是关于在 Swift 中,如何从异步任务外壳内部更新主线程上的文本视图的主要内容,如果未能解决你的问题,请参考以下文章