我想在函数完成后5秒继续触发。
以前我会在函数的末尾使用这个:
Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { self.function() }
但是我想用Swift 5.5
的async/await
。
如果我这样写:
func loadInfo() async {
async let info = someOtherAsyncFunc()
self.info = try? await info
await Task.sleep(5_000_000_000)
await loadInfo()
}
我得到一个警告,Function call causes an infinite recursion
和它不能真正取消。
这个编译得很好:
func loadInfo() async {
Task {
async let info = someOtherAsyncFunc()
self.info = try? await info
await Task.sleep(5_000_000_000)
if Task.isCancelled {
print("Cancelled")
}
else
{
print("Not cancelled")
await loadInfo()
}
}
}
,虽然它每5秒触发一次,但当我的SwiftUI
视图被驳回时,它会继续运行。我开始使用:
.onAppear {
loadInfo()
}
因为它都在同一个Task
上运行,而不是detached
,当视图被删除时,它不应该全部取消吗?
用async/await
实现这一目标的现代方法是什么?
您可以将任务保存在@State
变量中,然后在onDisappear(perform:)
视图消失时取消它。
工作的例子:
struct ContentView: View {
@State private var info: String?
@State private var currentTask: Task<Void, Never>?
var body: some View {
NavigationView {
VStack {
Text(info ?? "None (yet)")
.onAppear(perform: loadInfo)
.onDisappear(perform: cancelTask)
NavigationLink("Other view") {
Text("Some other view")
}
}
.navigationTitle("Task Test")
}
.navigationViewStyle(.stack)
}
private func loadInfo() {
currentTask = Task {
async let info = someOtherAsyncFunc()
self.info = try? await info
await Task.sleep(5_000_000_000)
guard !Task.isCancelled else { return }
loadInfo()
}
}
private func cancelTask() {
print("Disappear")
currentTask?.cancel()
}
private func someOtherAsyncFunc() async throws -> String {
print("someOtherAsyncFunc ran")
return "Random number: (Int.random(in: 1 ... 100))"
}
}
async await的目的是让您以同步的方式编写异步代码。因此,您可以删除递归函数并简单地写入:
.task {
repeat {
// code you want to repeat
print("Tick")
try? await Task.sleep(for: .seconds(5)) // exception thrown when cancelled by SwiftUI when this view disappears.
} while (!Task.isCancelled)
print("Cancelled")
}
我注意到print tick总是在主线程上,但是如果我把它移到它自己的async函数中,那么它就会正确地在不同的线程上运行。