Overview
Starting with version 1.7, The Composable Architecture supports Swift 5.9’s Observation framework and includes a backport called Perception for iOS 13 and later. Source: ObservationBackport.md:1-11ObservableState Macro
The@ObservableState macro marks your state as observable:
- Conforms your state to the
ObservableStateprotocol - Generates observation infrastructure
- Enables automatic view updates in SwiftUI
- Works on both iOS 17+ (using Observation) and iOS 13+ (using Perception)
iOS 17+ (Native Observation)
On iOS 17 and later, TCA uses Swift’s native Observation framework:Using @Bindable
For two-way bindings on iOS 17+, use SwiftUI’s@Bindable:
iOS 13-16 (Perception Backport)
For iOS 13 through 16, TCA provides the Perception framework as a backport.The @Perceptible Macro
For standalone models (not TCA state), use@Perceptible instead of @Observable:
WithPerceptionTracking
When using Perception on iOS 13-16, wrap your view body inWithPerceptionTracking:
Perception.Bindable
For bindings on iOS 13-16, usePerception.Bindable instead of SwiftUI’s @Bindable:
Cross-Platform Pattern
For apps supporting both iOS 17+ and earlier versions:Lazy View Closures
Many SwiftUI closures are lazy and execute after thebody is computed. These require their own WithPerceptionTracking:
Common Lazy Closures
These SwiftUI closures are lazy and need their ownWithPerceptionTracking:
ForEach { ... }List { ... }LazyVStack { ... }andLazyHStack { ... }NavigationLink(destination: { ... }).background { ... }.overlay { ... }.sheet(item:) { ... }.fullScreenCover(item:) { ... }
ObservableState Protocol
TheObservableState protocol is the foundation of TCA’s observation:
Identity Tracking
TCA tracks state identity to optimize view updates:Store Observation
TheStore type integrates with both Observation and Perception:
Mixing Legacy and Modern Features
Source: ObservationBackport.md:118-127 Migrate features incrementally:Platform Differences
visionOS
On visionOS, TCA always uses native Observation:iOS 17+
On iOS 17 and later, Store conforms to Observable:Best Practices
Always Use @ObservableState
Even if you’re only supporting iOS 17+, use@ObservableState instead of @Observable:
Wrap All View Bodies (iOS 13-16)
Consistently useWithPerceptionTracking:
Check for Runtime Warnings
If you see the purple runtime warning, check the stack trace to find where you’re accessing state without tracking:- Open the Issue Navigator (⌘5)
- Expand the warning
- Click through stack frames
- Find the line accessing state
- Add
WithPerceptionTracking
Prefer Structs for State
Always use structs for state, not classes:Performance Considerations
Observation Overhead
The observation system has minimal overhead:- State access is tracked automatically
- Only changed properties trigger view updates
- Identity-based diffing optimizes collections