SwiftUI performance is often discussed as one problem, but it has at least two distinct surfaces. A person feels runtime work when images reload or state is recreated. A developer feels build-time work when a deeply nested view expression becomes too expensive for the type checker.
The latest SwiftUI updates address both surfaces. The important part is to adopt the behavior intentionally and to remove work from the view lifecycle rather than merely moving it to another modifier.
Let AsyncImage honor HTTP caching first
AsyncImage now uses standard HTTP caching by default, respecting the cache headers sent by the server. When a person scrolls away from an image and back, a cacheable response can return immediately instead of starting another network request.
This is a useful default, but it depends on the server response. Verify Cache-Control, ETag, and image URL stability before assuming a feed will cache well. If every asset URL changes on each request, no client-side API can recover the cache hit.
For a request that needs a different policy or a larger app-specific cache, pass a URLRequest and supply a configured session:
enum ImageLoader {
static let session: URLSession = {
let configuration = URLSessionConfiguration.default
configuration.urlCache = URLCache(
memoryCapacity: 64 * 1024 * 1024,
diskCapacity: 256 * 1024 * 1024
)
return URLSession(configuration: configuration)
}()
}
AsyncImage(
request: URLRequest(url: imageURL, cachePolicy: .returnCacheDataElseLoad)
)
.asyncImageURLSession(ImageLoader.session)Do not create a URLSession in a row body. A shared session gives the cache a useful lifetime and makes its capacity an application-level decision. Measure scroll-back behavior and network requests before increasing a cache, especially on storage-constrained devices.
Keep Observable class state from being recreated
In projects built with Xcode 27 or later, @State uses the State() macro. When the stored value is a class, SwiftUI initializes and stores it lazily once for the view's lifetime. This removes the discarded class instances that older view reinitializations could create.
@Observable
final class SearchStore {
var query = ""
}
struct SearchScreen: View {
@State private var store = SearchStore()
var body: some View {
SearchResults(query: store.query)
}
}The ownership rule remains the same: use @State when the view creates and owns the store. If a parent owns it, pass the observable value down instead of creating another instance. Lazy initialization reduces waste, but it does not fix an unclear state hierarchy.
This macro change can expose a source incompatibility. Do not give a state property a default value and then assign it again inside init. Declare the property without a default when initialization must depend on initializer input, then assign it once.
Reduce builder ambiguity before splitting views blindly
Complex nested Section, Group, and ForEach expressions can force the compiler to explore several possible result types before it can type-check the actual view content. The unified ContentBuilder improves this path in Xcode 27 by allowing type-agnostic content assembly where SwiftUI previously used several specialized builders.
That does not mean any enormous body becomes easy to maintain. Keep subviews around meaningful layout or state boundaries. The new builder reduces unnecessary compiler work, while a clear view structure still reduces cognitive work for the team.
If a project changes builders or adopts the new @State behavior, read the migration guidance and build the relevant targets early. Compiler errors in this area are often a signal that the old code was assigning state in two places or relying on an ambiguous builder context.
Measure the user path and the build path
Use runtime tools to confirm image requests, cache hits, and object allocation. Use a clean Xcode build to compare type-checking time for the views that were previously slow. Treat both as baseline measurements, not a promise that every screen improves automatically.
The productive outcome is modest but valuable: images stop being re-fetched without a reason, state-owning views stop creating throwaway classes, and the compiler has fewer type paths to explore. Those changes make SwiftUI feel faster to the person using the app and less resistant to the developer changing it.
