I'm looking for clarification on a SwiftUI performance point mentioned in the recent Optimize your app's speed and efficiency | Meet with Apple video.
(YouTube link not allowed, but the video is available on the Apple Developer channel.)
At the 1:48:50 mark, the presenter says:
Writing a value to the
Environmentdoesn't only affect the views that read the key you're updating. It updates any view that reads from anyEnvironmentkey. [abbreviated quote]
That statement seems like a big deal if your app relies heavily on Environment values.
Context
I'm building a macOS application with a traditional three-panel layout. At any given time, there are many views on screen, plus others that exist in the hierarchy but are currently hidden (for example, views inside tab views or collapsed splitters).
Nearly every major view reads something from the environment—often an @Observable object that acts as a service or provider.
However, there are a few relatively small values that are written to the environment frequently, such as:
- The selected tab index
- The currently selected object on a canvas
The Question
Based on the presenter's statement, I’m wondering:
- Does writing any value to the environment really cause all views in the entire SwiftUI view hierarchy that read any environment key to have their
bodyre-evaluated? - Do environment writes only affect child views, or do they propagate through the entire SwiftUI hierarchy?
Example:
View A
└─ View B
├─ View C
└─ View D
If View B updates an environment value, does that affect only C and D, or does it also trigger updates in A and B (assuming each view has at least one @Environment property)?
Possible Alternative
If all views are indeed invalidated by environment writes, would it be more efficient to “wrap” frequently-changing values inside an @Observable object instead of updating the environment directly?
// Pseudocode
@Observable final class SelectedTab {
var index: Int
}
ContentView()
.environment(\.selectedTab, selectedTab)
struct TabView: View {
@Environment(\.selectedTab) private var selectedTab
var body: some View {
Button("Action") {
// Would this avoid invalidating all views using the environment?
selectedTab.index = 1
}
}
}
Summary
From what I understand, it sounds like the environment should primarily be used for stable, long-lived objects—not for rapidly changing values—since writes might cause far more view invalidations than most developers realize.
Is that an accurate interpretation?
Follow-Up
In Xcode 26 / Instruments, is there a way to monitor writes to @Environment?
Hi! I'll try to provide some clarification:
Does writing any value to the environment really cause all views in the entire SwiftUI view hierarchy that read any environment key to have their body re-evaluated?
No, the view bodies are only run if the body uses the environment value for the key(s) used in that view and the value changes. But in SwiftUI, an update doesn't always cause the view body to run again, but there is still a cost associated with these updates. In Instruments, these updates are labeled as "skipped".
The demo in the video shows that the cumulative cost of all the skipped updates (seen in the "Consequences" detail view) is significant, even without their associated view bodies running (the view that demonstrates this in the video is called CardBackView).
Do environment writes only affect child views, or do they propagate through the entire SwiftUI hierarchy?
Great question! This only applies to child views of the .environment modifier that you're writing with. So if View B uses an .environment modifier on both View C and View D, and those views read any environment key(s), that would cause those to update (along with their children), although their view bodies may be skipped if the keys they read are unrelated.
If all views are indeed invalidated by environment writes, would it be more efficient to “wrap” frequently-changing values inside an @Observable object instead of updating the environment directly?
To reiterate, all views are not invalidated, but they are in fact updated, even if their bodies end up getting skipped. You can observe this behavior for yourself using Instruments and looking for skipped updates. However, for avoiding those updates, @Observable is a great choice. This is the exact method suggested in the video.
From what I understand, it sounds like the environment should primarily be used for stable, long-lived objects—not for rapidly changing values—since writes might cause far more view invalidations than most developers realize.
This is spot on. It's best not to write the environment in hot paths, like the one demonstrated in the video. Remember that not only your views are dependent on the environment; SwiftUI views are as well, so it's not only your own updates you have to worry about.
In Xcode 26 / Instruments, is there a way to monitor writes to @Environment?
Yes, look for EnvironmentWriter in Instruments, in both the lists of updates and the Cause & Effect graph. This is shown in the demos in the talk you linked.