Hi Eytan, please read the posts from April 2023, above yours. This was a known issue and hasn’t been fixed in the book, so there’s no workaround for it.
Timer needs a runloop on the thread where it is scheduled, otherwise it will never fire. We can force it to the main thread which has a run loop by wrapping the code inside the AsyncStream trailing closure in a Main-Actor bound Task:
...
let counter = AsyncStream<String> { continuation in
Task { @MainActor in
// your challenge code goes here
}
}
...
However, we still get the warnings about Swift 6 language mode, which makes me wonder if using a Timer is a good idea. I tried wrapping countdown in an Actor. This way we need to await when accessing it and the warnings go away. But we need to create yet another Task inside the timer closure. At this point we get another Swift 6 warning when we invalidate the timer, because Timer is not Sendable…
If we avoid using Timer, we can clean up those warnings quite nicely:
func countdown(to message: String) async throws {
guard !message.isEmpty else { return }
let counter = AsyncStream { continuation in
Task {
for tick in (0...3).reversed() {
try await Task.sleep(for: .seconds(1))
try Task.checkCancellation()
switch tick {
case (1...):
continuation.yield("\(tick)...")
default:
continuation.yield("🎉 " + message)
continuation.finish()
}
}
}
}
for await countdownMessage in counter {
try await say(countdownMessage)
}
}