The Most Confusing Chapter in SwiftUI’s History: The iOS 17 Beta 5 Betrayal
If you were a SwiftUI developer in the summer of 2023, you remember the feeling. WWDC had just dropped. The heavens opened, and out came the Observation Framework. We were told to delete our ObservableObject protocols, throw @Published in the bin, and embrace the clean, macro-powered future of @Observable.
But then, iOS 17 Beta 5 happened. And it left a scar on the documentation—and our architectures—that hasn’t fully healed even in 2026.
The Promise: “@State for Everything!”
In the early betas, Apple made a bold move. They updated the @State property wrapper to work with the new @Observable classes.
Historically, @State was the playground for Value Types. Because structs are stack-allocated, SwiftUI has no problem re-initializing the default value every time the View is recreated—it’s cheap, ephemeral, and safe. But Objects are a different beast. They live on the Heap.
To bridge this gap, Apple initially gave @State an @autoclosure version of its initializer for @Observable objects. This was the holy grail: it meant the heavy heap allocation only happened once. SwiftUI would “reach into” the closure only when it actually needed to create the storage for the first time. You could safely write:
@State private var coordinator = Coordinator()
The Beta 1-4 “Golden Era” Header (approximate):
// This allowed for stable object initialization
@frozen @propertyWrapper
public struct State<Value> : DynamicProperty {
// The @autoclosure meant the object was only created when needed
public init(wrappedValue thunk: @autoclosure @escaping () -> Value)
}
The Beta 5 “Rug Pull”
Then, on July 27, 2023, the floor fell out. Apple quietly removed the @autoclosure version of the initialiser.
The Post-Beta 5 “Modern” Header:
// The @autoclosure is gone. The value is now initialized eagerly.
@frozen @propertyWrapper
public struct State<Value> : DynamicProperty {
public init(wrappedValue value: Value)
}
Without that closure, we entered the “Double-Allocation Danger Zone.” Because @State reverted to its eager initialization roots, your Coordinator() is now instantiated every single time the parent View is re-evaluated—even if SwiftUI ends up discarding that new instance and using the old one from its internal storage.
The Memory Minefield
Where we are today is a performance trap. If you use @State with an @Observable object:
- Heap Leakage: You are constantly allocating memory on the heap for objects that are immediately thrown away. While ARC eventually cleans them up, the churn is real.
- The Init Cascade: If your
Coordinatordoes any work in itsinit—like setting up a database, starting a timer, or initializing other objects—that work is now being performed on every single frame.
Regression: The Return of the “Dark Ages”
As the community scrambled for a solution, the documentation began recommending an Optional workaround:
This feels eerily familiar because it is. This is exactly what we had to do in the “Dark Ages” before @StateObject existed. We were forced to manually manage lifecycles: initializing in onAppear and nil-ing out in onDisappear.
@State private var coordinator: Coordinator?
...
if let coordinator {
MyView(coordinator: coordinator)
}
.onAppear {
if coordinator == nil {
coordinator = Coordinator()
}
}
.onDisappear {
coordinator = nil
}
But as any veteran dev knows, onAppear and onDisappear are notoriously unreliable. Their behavior changes depending on whether your view is in a List, a NavigationStack, or a Tabview. We are back to a time where our data’s lifecycle is tethered to the whims of the UI’s visibility—the exact problem @StateObject was invented to solve.
The Verdict: A “Broken” Modernity
By removing the @autoclosure, Apple effectively made @State an “unsafe” choice for heavy objects. It turned what was supposed to be a modern simplification into a performance trap that punishes developers for following the most intuitive syntax.
Today, many look at @StateObject as “legacy,” but it remains the most robust tool for stable object lifecycles. It is the only one left that actually respects the “Initialize Once” contract. Until @State regains its closure, the “modern” way remains a ghost of a better era that lasted only four betas.