From what I understand, @State properties are stored outside the actual View struct. This explains why: a) modifying them doesn’t mutate the view itself, and b) their values persist across re-renders.
While building my own flavour of SwiftUI framework – BlinkUI – I ran into a question that’s been bugging me:
How does SwiftUI identifies the @State property and uses it?
In my implementation, I ended up using Swift’s Mirror API to reflect on the view and extract state properties like this:
private func populateInternals(forView view: any View) {
let mirror = Mirror(reflecting: view)
mirror.children.forEach { (label, child) in
if let state = child as? AnyState {
// Handle the state property here
}
}
}
This approach feels a bit hacky, so I am hoping Apple uses a more elegant solution. Does anyone know what mechanism Apple might be using under the hood?
I wouldn’t expect it be less hacky. For what it worth, it might be even more trickier.
I’m not sure we can have the answer to this question, but if to think of how it might be achieved, the simplest (not easy still) solution I can think of would be to handle some kind of, probably global, registry to the State under the hood. Then, when any instance of a State gets created it registers itself there, so that rendering engine can later access it. I guess each view in that case has some way to uniquely identify yourself so that this matching can be done as well.
I believe SwiftUI doesn't use Mirror, but instead uses type metadata directly to learn the layout of each View-conforming type on demand.
By looking at the metadata directly, SwiftUI gains some efficiency advantages over Mirror. First, SwiftUI can cache layout information in whatever format it finds most convenient. Second, SwiftUI can poke around in View values without necessarily going through Any conversions (which Mirror always performs) and without creating AnyCollection instances and corresponding iterators.
I think SwiftUI also uses type metadata to decompose instances of TupleView.
I'm not sure if the type metadata documentation is currently complete or up to date. I've had trouble in the past with it being neither. But the SwiftUI engineers probably got some help from the Swift language engineers. There are also some open source packages for examining Swift metadata, like Azoy/Echo, that I have found very educational.
@curt had a talk that gave a little peek into how the SwiftUI infra handles this idea. This was at the SwiftUI summit in February at Apple. AFAIK this complete talk has not been released as public. We were told that taking photos of slides was ok but we did not have the ability to record it for ourselves. Possibly there is a WWDC 25 talk coming up with another peek "behind the curtain"?
Does that mean it's mainly performed by @State property wrapper? That's what I thought too, but there seems to be a missing link. SwiftUI needs to know which view (e.g. view ID) a state belongs to. The slide doesn't show that relation. It's unlikely @State property wrapper can determine it itself. Is this where type metadata comes into play?
BTW, what's "Ocfb" in the slide? (it's the text in the small red box in 'SwiftUI Internal Storage' block)
This bit, on its own, could be achieved quite easily:
class C {
var property: Int = 0
}
struct S: Hashable {
static func == (lhs: Self, rhs: Self) -> Bool { lhs.c === rhs.c }
func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(c))
}
var c: C
var property: Int {
get { c.property }
nonmutating set { c.property = newValue }
}
}
let a = S(c: C())
let b = a
a.property = 42
precondition(a == b)
I'm not saying that this is what SwiftUI actually using (perhaps more like this).
There's also the DynamicType protocol, which one may leverage for triggering view updates for custom property wrapper state-like types. The state property wrapper type itself also conforms to this protocol.
In SwiftCrossUI I've gone the mirror route, although I may switch to a more low-level approach in future once it becomes a performance issue (there are currently much bigger bottlenecks). I've got a DynamicProperty protocol that property wrappers like State and Environment can conform to. Before my view graph evaluates a view's body, it first locates all the dynamic properties; giving them the current environment and their own previous value so that they can do things like carry across their internal class storage and update their value based on the current environment.
I've intentionally not looked too deep into SwiftUI's own implementation approach, so that I can make a properly independent implementation with new solutions.
I also support non-Apple platforms, so anything that relies on ABI stability is probably not viable for use in my implementation.
Really interesting discussion! Thanks, everyone -- I found a lot of useful reference points to better understand how SwiftUI works / might be working, which I can now apply to improve BlinkUI!!
You can have a Singlton storage for all views being created/rendered. For each View being created you can create an identifier and have custom equality check.
Then you can use the identifier to store/retrive values from the said Singleton. If there's already something there -> check for equality check. If it doesn't match, then this view should be rerendered.
This is very similar to the approach they took for Observable framework.
Since all Views are @MainActor you can isolate your Singleton to @MainActor (or whatever actor your BlinkUI is using) as well.
Did you mean @State is implemented using the internal subscript() API? But that API requires the enclosing instance's type is class, instead of struct.
I use it to list properties as well as read and write them (you can write the whole State struct to the view). It's important that everything you write to is a variable (and not a let constant) so that you don't break compilation invariants at runtime (because the library can write to let constants if you ask it to).
I expect that SwiftUI uses a similar mechanism.
But if I were to redo it in 2025 I would rather use macros to generate listing / getting / setting those values.
I assumed it could be possible to use it with structs, apologies for the incorrect information.
That was my assumption for how to extract identifier out of the View type. Considering that it is only available to classes, I think a Mirror is used then.
But I still believe a global storage is used in SwiftUI (or something similar).
I speculate this approach because of some other APIs, e.g onChange API.
nonisolated
func onChange<V>(
of value: V,
initial: Bool = false,
_ action: @escaping () -> Void
) -> some View where V : Equatable
This API, takes a L-Value as source and whenever this Value (which is backed by @State) gets changed, triggers the action closure.
Since this API does not accept a Binding/State/KeyPath and accepts the Value itself, this means, before this method is called, on @State's getter, a reference to the property or a KeyPath or sth must be stored somewhere (probably global), and onChange references that storage to see what was referenced. So it can start the tracking process. (Same approach as Observable).
This is the only explanation I could come-up with as to how onChange API could be working. If there are any other possibilities, I would appreciate if anyone could tell me.
Building on the above, this means onChange method and State should be able to have access to the same Storage.
Eiither by Enclosing PropertyWrapper (which I was mistaken)
Or by injecting a reference into @State by Mirror, before View.body gets called.
I have been puzzled why/how many APIs are designed like onChange in SwiftUI and this is the only explanation I could have come up with.
(I have used SwiftUI for quite a while. The following understanding is very likely wrong. SwiftUI document seems to suggest that it only invokes onChange method when value changes. So it needs to know old value to determine it.)
I don't think that's how it works. It might be simpler:
First, IIRC View.onChange() API works for not only state but also a regular property. When a regular property changes, the change itself isn't sufficient to cause view re-rendering (that is, calling body). This means SwiftUI framework doesn't need to track the old value.
If user needs to access the old value in closure, they can capture it using capture list.
In SwiftCrossUI, I implement onChange using an OnChangeModifier view which wraps the child view. This wrapper view has its own internal state, similar to the way that user-defined views can store persistent state with @State. It uses that internal state to store the previous value it was given and compare it against the current value. This is likely similar to what SwiftUI does, because as you've noted, we don't have a way of knowing where the value originated from. This approach allows the value passed to onChange to be any expression, not just a plain reference to a state property.
This also has the effect that onChange handlers only actually get run when the view body gets 'rendered' rather than immediately when the onChange modifier method is called, as is usual for SwiftUI's declarative modifiers.
Here's my View.onChange implementation. I just use @State to store the previous value in OnChangeModifier (rather than some more advanced internal storage) and perform the change checks in the update method (which SwiftCrossUI calls when it performs layout and other related update operations).