Files
swift-composable-architectu…/Sources/ComposableArchitecture/SharedState/SharedReader.swift
Stephen Celis d468b45abb wip
2024-10-17 14:17:30 -07:00

193 lines
5.6 KiB
Swift

#if canImport(Combine)
import Combine
#endif
/// A property wrapper type that shares a value with multiple parts of an application.
///
/// See the <doc:SharingState> article for more detailed information on how to use this property
/// wrapper, in particular <doc:SharingState#Read-only-shared-state>.
@dynamicMemberLookup
@propertyWrapper
public struct SharedReader<Value: Sendable> {
fileprivate let reference: any Reference<Value>
init(reference: any Reference<Value>) {
self.reference = reference
}
/// Creates a read-only shared reference from another read-only shared reference.
///
/// You don't call this initializer directly. Instead, Swift calls it for you when you use a
/// property-wrapper attribute on a binding closure parameter.
///
/// - Parameter projectedValue: A read-only shared reference.
public init(projectedValue: SharedReader) {
self = projectedValue
}
/// Unwraps a read-only shared reference to an optional value.
///
/// ```swift
/// @SharedReader(.currentUser) var currentUser: User?
///
/// if let sharedCurrentUser = SharedReader($currentUser) {
/// sharedCurrentUser // SharedReader<User>
/// }
/// ```
///
/// - Parameter base: A read-only shared reference to an optional value.
public init?(_ base: SharedReader<Value?>) {
guard let initialValue = base.wrappedValue
else { return nil }
func open(_ location: some Reference<Value?>) -> any Reference<Value> {
_ReferenceFromOptional(initialValue: initialValue, base: location)
}
self.init(reference: open(base.reference))
}
/// Creates a read-only shared reference from a shared reference.
///
/// - Parameter base: A shared reference.
public init(_ base: Shared<Value>) {
self = base.reader
}
/// Constructs a read-only shared value that remains constant.
///
/// This can be useful for providing ``SharedReader`` values to features in previews and tests:
///
/// ```swift
/// #Preview {
/// FeatureView(
/// store: Store(
/// initialState: Feature.State(count: .constant(42))
/// ) {
/// Feature()
/// }
/// )
/// )
/// ```
public static func constant(_ value: Value) -> Self {
Shared(value).reader
}
/// The underlying value referenced by the shared variable.
///
/// This property provides primary access to the value's data. However, you don't access
/// `wrappedValue` directly. Instead, you use the property variable created with the
/// ``SharedReader`` attribute. In the following example, the shared variable `topics` returns the
/// value of `wrappedValue`:
///
/// ```swift
/// struct State {
/// @SharedReader var subscriptions: [Subscription]
///
/// var isSubscribed: Bool {
/// !subscriptions.isEmpty
/// }
/// }
/// ```
public var wrappedValue: Value {
reference.value
}
/// A projection of the read-only shared value that returns a shared reference.
public var projectedValue: Self {
get { self }
set {
reference.touch()
self = newValue
}
}
/// Returns a shared reference to the resulting value of a given key path.
public subscript<Member>(
dynamicMember keyPath: KeyPath<Value, Member>
) -> SharedReader<Member> {
func open(_ location: some Reference<Value>) -> SharedReader<Member> {
SharedReader<Member>(
reference: _ReferenceAppendKeyPath(
base: location,
keyPath: sendableKeyPath(keyPath)
)
)
}
return open(reference)
}
@_disfavoredOverload
@available(
*, deprecated, message: "Use 'SharedReader($value.optional)' to unwrap optional shared values"
)
public subscript<Member>(
dynamicMember keyPath: KeyPath<Value, Member?>
) -> SharedReader<Member>? {
SharedReader<Member>(self[dynamicMember: keyPath])
}
#if canImport(Combine)
/// Returns a publisher that emits events when the underlying value changes.
public var publisher: AnyPublisher<Value, Never> {
func open(_ publisher: some Publisher<Value, Never>) -> AnyPublisher<Value, Never> {
publisher.eraseToAnyPublisher()
}
return open(self.reference.publisher)
}
#endif
}
extension SharedReader: @unchecked Sendable where Value: Sendable {}
extension SharedReader: Equatable where Value: Equatable {
public static func == (lhs: SharedReader, rhs: SharedReader) -> Bool {
lhs.wrappedValue == rhs.wrappedValue
}
}
extension SharedReader: Hashable where Value: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(self.wrappedValue)
}
}
extension SharedReader: Identifiable where Value: Identifiable {
public var id: Value.ID {
self.wrappedValue.id
}
}
extension SharedReader: Encodable where Value: Encodable {
public func encode(to encoder: any Encoder) throws {
do {
var container = encoder.singleValueContainer()
try container.encode(self.wrappedValue)
} catch {
try self.wrappedValue.encode(to: encoder)
}
}
}
extension SharedReader: CustomDumpRepresentable {
public var customDumpValue: Any {
self.wrappedValue
}
}
extension SharedReader
where
Value: RandomAccessCollection & MutableCollection,
Value.Index: Hashable & Sendable,
Value.Element: Sendable
{
/// Derives a collection of read-only shared elements from a read-only shared collection of
/// elements.
///
/// See the documentation for [`@Shared`](<doc:Shared>)'s ``Shared/elements`` for more
/// information.
public var elements: some RandomAccessCollection<SharedReader<Value.Element>> {
zip(self.wrappedValue.indices, self.wrappedValue).lazy.map { index, element in
self[index, default: DefaultSubscript(element)]
}
}
}