Shared
A property wrapper type that shares a value with other parts of the application and/or external systems.
@dynamicMemberLookup @propertyWrapper struct Shared<Value>
Overview
Use shared state to allow for multiple parts of your application to hold onto the same piece of mutable data. For example, you can have two different obsevable models hold onto a collection of data that is also synchronized to the file system:
// MeetingsList.swift
@Observable
class MeetingsListModel {
@ObservationIgnored
@Shared(.fileStorage(.meetingsURL)) var meetings: [Meeting] = []
}
// ArchivedMeetings.swift
@Observable
class ArchivedMeetingsModel {
@ObservationIgnored
@Shared(.fileStorage(.meetingsURL)) var meetings: [Meeting] = []
}
If either model makes a change to meetings
, the other model will instantly see those changes. And further, if the file on disk changes from an external write, both instances of @Shared
will also update to hold the freshest data.
Automatic persistence
The @Shared
property wrapper gives you a succinct and consistent way to persist any kind of data in your application. The library comes with 3 strategies: appStorage
, fileStorage
, and inMemory
.
The appStorage
strategy is useful for store small pieces of simple data in user defaults, such as settings:
@Shared(.appStorage("soundsOn")) var soundsOn = true
@Shared(.appStorage("hapticsOn")) var hapticsOn = true
@Shared(.appStorage("userSort")) var userSort = UserSort.name
The fileStorage
strategy is useful for persisting more complex data types to the file system by serializing the data to bytes:
@Shared(.fileStorage(.meetingsURL)) var meetings: [Meeting] = []
And the inMemory
strategy is useful for sharing any kind of data globably with the entire app, but it will be reset the next time the app is relaunched:
@Shared(.inMemory("events")) var events: [String] = []
See Persistence strategies for more information on leveraging the persistence strategies that come with the library, as well as creating your own strategies.
Use anywhere
It is possible to use @Shared
state essentially anywhere, including observable models, SwiftUI views, UIKit view controllers, and more. For example, if you have a simple view that needs access to some shared state but does not need the full power of an observable model, then you can use @Shared
directly in the view:
struct DebugMeetingsView: View {
@Shared(.fileStorage(.meetingsURL)) var meetings: [Meeting] = []
var body: some View {
ForEach(meetings) { meeting in
Text(meeting.title)
}
}
}
Similarly, if you need to use UIKit for a particular feature or have a legacy feature that can’t use SwiftUI yet, then you can use @Shared
directly in a view controller:
final class DebugMeetingsViewController: UIViewController {
@Shared(.fileStorage(.meetingsURL)) var meetings: [Meeting] = []
// ...
}
And to observe changes to meetings
so that you can update the UI you can either use the publisher
property or the observe
tool from our Swift Navigation library. See Observing changes to shared state for more information.
Testing
Features using the @Shared
property wrapper remain testable even though they interact with outside storage systems, such as user defaults and the file system. This is possible because each test gets a fresh storage system that is quarantined to only that test, and so any changes made to it will only be seen by that test.
See Testing for more information on how to test your features when using @Shared
.
Creating a persisted value
init(wrappedValue: @autoclosure () -> Value, some SharedKey<Value>
) Creates a shared reference to a value using a shared key.
Creating a shared value
init(value: sending Value
) init(projectedValue: Self
) Creates a shared reference from another shared reference.
Transforming a shared value
subscript<Member>(dynamicMember: WritableKeyPath<Value, Member>
) -> Shared<Member> Returns a shared reference to the resulting value of a given key path.
subscript<Member>(dynamicMember: KeyPath<Value, Member>
) -> SharedReader<Member> Returns a read-only shared reference to the resulting value of a given key path.
init?(Shared<Value?>
) Unwraps a shared reference to an optional value.
Accessing the value
var wrappedValue: Value
var projectedValue: Self
A projection of the shared value that returns a shared reference.
Isolating the value
func withLock<R>((inout Value) throws -> R, fileID: StaticString, filePath: StaticString, line: UInt, column: UInt
) rethrows -> R Perform an operation on shared state with isolated access to the underlying value.
Loading and saving the value
var isLoading: Bool
Whether or not an associated shared key is loading data from an external source.
func load(
) async throws Requests an up-to-date value from an external source.
func load(some SharedKey<Value>
) async throws Replaces a shared reference’s key and attempts to load its value.
init(require: some SharedKey<Value>
) async throws Creates a shared reference to a value using a shared key by loading it from its external source.
func save(
) async throws Requests the underlying value be persisted to an external source.
Error handling
var loadError: (any Error)?
An error encountered during the most recent attempt to load data.
var saveError: (any Error)?
An error encountered during the most recent attempt to save data.
SwiftUI integration
protocol RangeReplaceableCollection<Element>
A collection that supports replacement of an arbitrary subrange of elements with the elements of another collection.
SwiftUICore/Binding
Combine integration
var publisher: some Publisher<Value, Never>
Returns a publisher that emits events when the underlying value changes.