I want to keep firing a function 5 seconds after it completes.
Previously I would use this at the end of the function:
Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { self.function() }
But I am wanting to use Swift 5.5
's async/await
.
If I use something like this:
func loadInfo() async {
async let info = someOtherAsyncFunc()
self.info = try? await info
await Task.sleep(5_000_000_000)
await loadInfo()
}
I get a warning that the Function call causes an infinite recursion
and it's not really cancellable.
This compiles fine:
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()
}
}
}
and although it does fire every 5 seconds, it keeps running when my SwiftUI
view is dismissed.
I start it using:
.onAppear {
loadInfo()
}
As it's all running on the same Task
and not detached
should it not all cancel when the view is removed?
What is the modern way to achieve this with async/await
?
You can save the task in a @State
variable, and then cancel it when the view disappears with onDisappear(perform:)
.
Working example:
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))"
}
}