← All writing
HackerNoonJune 2026

SwiftUI's @State Is Now a Macro — What It Means for You

@State has always been quietly magical: you read the value directly, get a binding through the dollar-sign prefix, and let the framework own the storage. Now that Apple documents it as a macro, the public declaration is a wall of attached-accessor and peer attributes. It reads like a breaking change and mostly is not one — you still declare @State on a property that belongs to a single view, initialise it where you declare it, and keep it private.

What the macro spelling does explain is the behaviour that always seemed slightly arbitrary: how initialisation is deferred, why the old Optional workarounds are no longer needed, and why assigning state inside init() remains a trap rather than a shortcut.

It closes on how @State sits next to @Observable, @Binding and @Bindable, and on the fact that a macro is not free — it expands into real code with real compile-time cost.

What's inside

  • It walks through why the macro-based declaration explains a timing quirk that used to look arbitrary: under the old property-wrapper implementation, an object held in @State could get reconstructed on every parent re-render even though the stored state itself was never lost.
  • Using a small @Observable example, it shows the initializer now runs exactly once, at the moment the state's storage is created, which is also why the earlier habit of declaring the property as an optional and filling it in later inside a task stops being necessary.
  • It draws a firm line the macro does not erase: setting @State from within a view's own initializer still re-runs every time that view is rebuilt, so a value that depends on input from a parent still has to arrive through the view's own properties or be rebuilt through a task tied to that input.
  • It recaps Apple's pairing rules for @State alongside @Observable, @Binding, and @Bindable — when a view should hold the reference itself, when a child needs the ability to replace or clear it, and when a child only needs to edit the object's own fields — as a short set of patterns worth memorizing.
  • It ends on the reminder that a macro is not a free abstraction: it still expands into generated code, which has a cost in compile times and in debugging.
The takeaway

The overall message is reassuring rather than alarming: the macro spelling mostly makes @State's existing behavior explicit, resolves one long-standing surprise around lazy initialization, and leaves the boundaries around view initializers and dependency injection exactly where they already were.

Read the full macro breakdown, with code, on HackerNoon ↗