Sharing state is the process of letting many features have access to the same data so that when any feature makes a change, it is instantly visible to every other feature. The Composable Architecture provides tools for sharing state with many parts of your application.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/pointfreeco/swift-composable-architecture/llms.txt
Use this file to discover all available pages before exploring further.
Understanding shared state
Because the Composable Architecture prefers modeling domains with value types rather than reference types, sharing state can be tricky. The library comes with tools from the Sharing library to handle this.There are two main kinds of shared state: explicitly passed state and persisted state. And there are 3 persistence strategies: in-memory, user defaults, and file storage.
Explicit shared state
This is the simplest kind of shared state. It allows you to share state amongst many features without any persistence. The data is only held in memory. Now any mutation the child makes tocount will be instantly reflected in the parent’s count too.
Persisted shared state
Sometimes you want to share state with the entire application without passing it around explicitly.In-memory persistence
Keeps data in memory and makes it available everywhere, but doesn’t persist across app launches:When using a persistence strategy with
@Shared, you must provide a default value.App Storage (User Defaults)
Automatically persists changes to user defaults:File storage
For complex data types, use file storage with JSON serialization:Custom persistence
You can create custom persistence strategies by conforming toSharedKey:
Observing changes
The@Shared property wrapper exposes a publisher to observe changes:
Initialization rules
Property wrappers have special initialization rules. Here are the patterns:Non-persisted, parent owns source of truth
Non-persisted, parent owns source of truth
The initializer takes a
Shared value:Non-persisted, child owns source of truth
Non-persisted, child owns source of truth
The initializer takes a plain value:
With persistence strategy
With persistence strategy
Use the
wrappedValue initializer:Deriving shared state
You can derive shared state for sub-parts of existing shared state:Shared<String> from Shared<SignUpData>, allowing features to hold only the minimum shared state they need.
Concurrent mutations
Shared state is technically a reference, which means race conditions are possible. UsewithLock to mutate safely:
Testing shared state
Shared state behaves differently from regular state but can still be tested exhaustively:TestStore and @Shared type work together to snapshot state before and after actions, allowing exhaustive assertions.
Testing with effects
If shared state is mutated in an effect:Testing with persistence
The.appStorage and .fileStorage strategies do extra work for testing:
.appStorageuses a non-persisting user defaults by default.fileStorageuses a mock file system
Type-safe keys
Add type safety by extendingSharedReaderKey:
Best practices
Use explicit sharing
Prefer passing
@Shared references explicitly over global persistence strategiesLock mutations
Always use
withLock when mutating shared stateDerive when possible
Use derived shared state to give child features minimal access
Type-safe keys
Create type-safe keys for better compiler checking
Read-only shared state
For read-only access, use@SharedReader: