Here is a simple class that implements a timer:
import AsyncAlgorithms
final class Timer {
private var task: Task<Void, Never>?
init() {
let id = ObjectIdentifier(self)
print(id, "init")
}
deinit {
let id = ObjectIdentifier(self)
print(id, "deinit")
self.task?.cancel()
}
func start() {
if let _ = self.task {
return
}
let id = ObjectIdentifier(self)
self.task = Task.immediate {
print(id, "start")
defer {
print(id, "stop")
}
for await _ in AsyncTimerSequence.repeating(every: .seconds(1.0)) {
let now = Date.now
print(
id,
now.formatted(
date: .omitted,
time: .standard
)
)
}
}
}
}
And here is a simple SwiftUI app to start a timer:
import SwiftUI
@main
struct StateDemoApp: App {
@State private var timer = Timer()
init() {
self.timer.start()
}
var body: some Scene {
WindowGroup {
EmptyView()
}
}
}
Launching the app from Xcode 26.6 runs correctly:
ObjectIdentifier(0x0000000c0c96c020) init
ObjectIdentifier(0x0000000c0c96c020) start
ObjectIdentifier(0x0000000c0c96c020) 10:40:19 PM
ObjectIdentifier(0x0000000c0c96c020) 10:40:20 PM
ObjectIdentifier(0x0000000c0c96c020) 10:40:21 PM
...
...
...
Launching the app from Xcode 27 Beta 3 breaks:
ObjectIdentifier(0x0000000a22c245a0) init
ObjectIdentifier(0x0000000a22c245a0) start
ObjectIdentifier(0x0000000a22c245a0) deinit
ObjectIdentifier(0x0000000a22a2ca80) init
ObjectIdentifier(0x0000000a22c245a0) stop
This makes no sense to me. Why did my Task stop? Why was my Timer deallocated?
Here is a repro:
https://github.com/vanvoorden/2026-07-16
Please let me know if you have any ideas why this happened. Thanks!
Hello @vanvoorden,
I believe this is related to the "Important" note found in TN3211: Resolving SwiftUI source incompatibilities for State and ContentBuilder:
Paraphrasing:
Assigning to a @State property that already has an inline initial value is not supported and does not produce the expected behavior at runtime.
I'm reaching out to some folks to see if this needs to be clarified further in the technote, clearly you are not "assigning" a value in your example, but I still believe what you are doing is producing unexpected behavior with the State macro due to the same underlying reason.
I believe these prints are from your call to self.timer.start() in your App's init:
ObjectIdentifier(0x0000000a22c245a0) init ObjectIdentifier(0x0000000a22c245a0) start ObjectIdentifier(0x0000000a22c245a0) deinit
According to my theory, I don't believe the @State has actually been initialized at this point, so your call to start() actually initializes a Timer, starts it, and then as soon as that scope exits ARC destroys that Timer.
Then, a little bit later, the @State managed Timer is actually created, resulting in these logs:
ObjectIdentifier(0x0000000a22a2ca80) init ObjectIdentifier(0x0000000a22c245a0) stop
(well, the ordering and identifiers might be a little out of order in my post here, but you get the idea)
If you call start() in a .task on the view instead of in the App's init, I believe you will see what you expect.
-- Greg