我有一个ObservableObject
,可以做CPU限制的繁重工作:
import Foundation
import SwiftUI
@MainActor
final class Controller: ObservableObject {
@Published private(set) var isComputing: Bool = false
func compute() {
if isComputing { return }
Task {
heavyWork()
}
}
func heavyWork() {
isComputing = true
sleep(5)
isComputing = false
}
}
我使用Task
在后台使用新的并发特性进行计算.这需要使用@MainActor
属性来确保在主参与者上执行所有UI更新(这里绑定到isComputing
属性).
然后,我有以下视图,其中显示了一个计数器和一个启动计算的按钮:
struct ContentView: View {
@StateObject private var controller: Controller
@State private var counter: Int = 0
init() {
_controller = StateObject(wrappedValue: Controller())
}
var body: some View {
VStack {
Text("Timer: \(counter)")
Button(controller.isComputing ? "Computing..." : "Compute") {
controller.compute()
}
.disabled(controller.isComputing)
}
.frame(width: 300, height: 200)
.task {
for _ in 0... {
try? await Task.sleep(nanoseconds: 1_000_000_000)
counter += 1
}
}
}
}
问题是,计算似乎阻塞了整个UI:计数器冻结.
Why does the UI freeze and how to implement 100 in such a way it does not block the UI updates?
我试过的
- 每次更新发布的属性时,使用
heavyWork
和异步方法以及await Task.yield()
似乎都有效,但这既麻烦又容易出错.此外,它允许some次用户界面更新,但不能在随后的Task.yield()
次调用之间进行. - 如果我们忽略紫色警告,即应该对主参与者进行UI更新,那么删除
@MainActor
属性似乎是可行的(尽管这不是一个有效的解决方案).
编辑1
多亏了@Bradley提出的答案,我找到了这个解决方案,它能如预期的那样工作(非常接近通常的DispatchQueue
方式):
@MainActor
final class Controller: ObservableObject {
@Published private(set) var isComputing: Bool = false
func compute() {
if isComputing { return }
Task.detached {
await MainActor.run {
self.isComputing = true
}
await self.heavyWork()
await MainActor.run {
self.isComputing = false
}
}
}
nonisolated
func heavyWork() async {
sleep(5)
}
}