diff --git a/Examples/Packages/iOS/Sources/ExampleVoiceMemo/Atoms/VoiceMemoRowAtoms.swift b/Examples/Packages/iOS/Sources/ExampleVoiceMemo/Atoms/VoiceMemoRowAtoms.swift index 272711a2..69caacf9 100644 --- a/Examples/Packages/iOS/Sources/ExampleVoiceMemo/Atoms/VoiceMemoRowAtoms.swift +++ b/Examples/Packages/iOS/Sources/ExampleVoiceMemo/Atoms/VoiceMemoRowAtoms.swift @@ -7,7 +7,7 @@ import SwiftUI struct IsPlayingAtom: StateAtom { let voiceMemo: VoiceMemo - var key: some Hashable { + var key: URL { voiceMemo.url } @@ -39,7 +39,7 @@ struct IsPlayingAtom: StateAtom { struct PlayingElapsedTimeAtom: PublisherAtom { let voiceMemo: VoiceMemo - var key: some Hashable { + var key: URL { voiceMemo.url } @@ -58,7 +58,7 @@ struct PlayingElapsedTimeAtom: PublisherAtom { struct AudioPlayerAtom: ValueAtom { let voiceMemo: VoiceMemo - var key: some Hashable { + var key: URL { voiceMemo.url } diff --git a/Sources/Atoms/Core/Atom/Atom.swift b/Sources/Atoms/Core/Atom/Atom.swift index 563ac361..ca683e22 100644 --- a/Sources/Atoms/Core/Atom/Atom.swift +++ b/Sources/Atoms/Core/Atom/Atom.swift @@ -4,7 +4,7 @@ /// and is immediately released when no longer watched. public protocol Atom: Sendable { /// A type representing the stable identity of this atom. - associatedtype Key: Hashable + associatedtype Key: Hashable & Sendable /// The type of value that this atom produces. associatedtype Produced diff --git a/Sources/Atoms/Core/Atom/ModifiedAtom.swift b/Sources/Atoms/Core/Atom/ModifiedAtom.swift index 4da7949f..0b849fc6 100644 --- a/Sources/Atoms/Core/Atom/ModifiedAtom.swift +++ b/Sources/Atoms/Core/Atom/ModifiedAtom.swift @@ -6,7 +6,7 @@ public struct ModifiedAtom: Atom where Node. public typealias Produced = Modifier.Produced /// A type representing the stable identity of this atom. - public struct Key: Hashable { + public struct Key: Hashable, Sendable { private let atomKey: Node.Key private let modifierKey: Modifier.Key diff --git a/Sources/Atoms/Core/AtomKey.swift b/Sources/Atoms/Core/AtomKey.swift index 7f9de56c..57bd2cf4 100644 --- a/Sources/Atoms/Core/AtomKey.swift +++ b/Sources/Atoms/Core/AtomKey.swift @@ -1,5 +1,5 @@ -internal struct AtomKey: Hashable, CustomStringConvertible { - private let key: AnyHashable +internal struct AtomKey: Hashable, Sendable, CustomStringConvertible { + private let key: UnsafeUncheckedSendable private let type: ObjectIdentifier private let scopeKey: ScopeKey? private let anyAtomType: Any.Type @@ -20,7 +20,7 @@ internal struct AtomKey: Hashable, CustomStringConvertible { } init(_ atom: Node, scopeKey: ScopeKey?) { - self.key = AnyHashable(atom.key) + self.key = UnsafeUncheckedSendable(atom.key) self.type = ObjectIdentifier(Node.self) self.scopeKey = scopeKey self.anyAtomType = Node.self diff --git a/Sources/Atoms/Core/Modifier/AtomModifier.swift b/Sources/Atoms/Core/Modifier/AtomModifier.swift index ff920616..78d15adf 100644 --- a/Sources/Atoms/Core/Modifier/AtomModifier.swift +++ b/Sources/Atoms/Core/Modifier/AtomModifier.swift @@ -11,7 +11,7 @@ public extension Atom { /// A modifier that you apply to an atom, producing a new value modified from the original value. public protocol AtomModifier: Sendable { /// A type representing the stable identity of this modifier. - associatedtype Key: Hashable + associatedtype Key: Hashable & Sendable /// A type of base value to be modified. associatedtype Base diff --git a/Sources/Atoms/Core/OverrideKey.swift b/Sources/Atoms/Core/OverrideKey.swift index 06a654d6..25a8ec0e 100644 --- a/Sources/Atoms/Core/OverrideKey.swift +++ b/Sources/Atoms/Core/OverrideKey.swift @@ -4,7 +4,7 @@ internal struct OverrideKey: Hashable, Sendable { @usableFromInline init(_ atom: Node) { - let key = AnyHashable(atom.key) + let key = UnsafeUncheckedSendable(atom.key) let type = ObjectIdentifier(Node.self) identifier = .node(key: key, type: type) } @@ -17,8 +17,8 @@ internal struct OverrideKey: Hashable, Sendable { } private extension OverrideKey { - enum Identifier: Hashable, @unchecked Sendable { - case node(key: AnyHashable, type: ObjectIdentifier) + enum Identifier: Hashable, Sendable { + case node(key: UnsafeUncheckedSendable, type: ObjectIdentifier) case type(ObjectIdentifier) } } diff --git a/Sources/Atoms/Core/ScopeKey.swift b/Sources/Atoms/Core/ScopeKey.swift index 7038a3d5..1ae751ef 100644 --- a/Sources/Atoms/Core/ScopeKey.swift +++ b/Sources/Atoms/Core/ScopeKey.swift @@ -1,5 +1,5 @@ @usableFromInline -internal struct ScopeKey: Hashable, CustomStringConvertible { +internal struct ScopeKey: Hashable, Sendable, CustomStringConvertible { final class Token {} private let identifier: ObjectIdentifier diff --git a/Sources/Atoms/Core/StoreContext.swift b/Sources/Atoms/Core/StoreContext.swift index a327b914..fb6dcede 100644 --- a/Sources/Atoms/Core/StoreContext.swift +++ b/Sources/Atoms/Core/StoreContext.swift @@ -440,7 +440,7 @@ private extension StoreContext { lazy var shouldKeepAlive = !key.isScoped && store.state.caches[key].map { $0.atom is any KeepAlive } ?? false lazy var isChildrenEmpty = store.graph.children[key]?.isEmpty ?? true lazy var isSubscriptionEmpty = store.state.subscriptions[key]?.isEmpty ?? true - lazy var shouldRelease = !shouldKeepAlive && isChildrenEmpty && isSubscriptionEmpty + let shouldRelease = !shouldKeepAlive && isChildrenEmpty && isSubscriptionEmpty guard shouldRelease else { return diff --git a/Sources/Atoms/Core/UnsafeUncheckedSendable.swift b/Sources/Atoms/Core/UnsafeUncheckedSendable.swift index 781e196b..90f3f323 100644 --- a/Sources/Atoms/Core/UnsafeUncheckedSendable.swift +++ b/Sources/Atoms/Core/UnsafeUncheckedSendable.swift @@ -7,3 +7,6 @@ internal struct UnsafeUncheckedSendable: @unchecked Sendable { self.value = value } } + +extension UnsafeUncheckedSendable: Equatable where Value: Equatable {} +extension UnsafeUncheckedSendable: Hashable where Value: Hashable {} diff --git a/Sources/Atoms/Modifier/AnimationModifier.swift b/Sources/Atoms/Modifier/AnimationModifier.swift index 159cd6c8..33951815 100644 --- a/Sources/Atoms/Modifier/AnimationModifier.swift +++ b/Sources/Atoms/Modifier/AnimationModifier.swift @@ -41,7 +41,7 @@ public struct AnimationModifier: AtomModifier { public typealias Produced = Produced /// A type representing the stable identity of this atom associated with an instance. - public struct Key: Hashable { + public struct Key: Hashable, Sendable { private let animation: Animation? fileprivate init(animation: Animation?) { diff --git a/Sources/Atoms/Modifier/ChangesModifier.swift b/Sources/Atoms/Modifier/ChangesModifier.swift index 4d37405f..687dfed1 100644 --- a/Sources/Atoms/Modifier/ChangesModifier.swift +++ b/Sources/Atoms/Modifier/ChangesModifier.swift @@ -40,7 +40,7 @@ public struct ChangesModifier: AtomModifier { public typealias Produced = Produced /// A type representing the stable identity of this atom associated with an instance. - public struct Key: Hashable {} + public struct Key: Hashable, Sendable {} /// A unique value used to identify the modifier internally. public var key: Key { diff --git a/Sources/Atoms/Modifier/ChangesOfModifier.swift b/Sources/Atoms/Modifier/ChangesOfModifier.swift index 2021ec1a..68ae5271 100644 --- a/Sources/Atoms/Modifier/ChangesOfModifier.swift +++ b/Sources/Atoms/Modifier/ChangesOfModifier.swift @@ -48,22 +48,35 @@ public struct ChangesOfModifier: AtomModifier { /// A type of value the modified atom produces. public typealias Produced = Produced - /// A type representing the stable identity of this modifier. - public struct Key: Hashable { - private let keyPath: KeyPath + #if compiler(>=6) || hasFeature(InferSendableFromCaptures) + /// A type representing the stable identity of this modifier. + public struct Key: Hashable, Sendable { + private let keyPath: KeyPath & Sendable - fileprivate init(keyPath: KeyPath) { - self.keyPath = keyPath + fileprivate init(keyPath: KeyPath & Sendable) { + self.keyPath = keyPath + } } - } - #if compiler(>=6) || hasFeature(InferSendableFromCaptures) private let keyPath: KeyPath & Sendable internal init(keyPath: KeyPath & Sendable) { self.keyPath = keyPath } + + /// A unique value used to identify the modifier internally. + public var key: Key { + Key(keyPath: keyPath) + } #else + public struct Key: Hashable, Sendable { + private let keyPath: UnsafeUncheckedSendable> + + fileprivate init(keyPath: UnsafeUncheckedSendable>) { + self.keyPath = keyPath + } + } + private let _keyPath: UnsafeUncheckedSendable> private var keyPath: KeyPath { _keyPath.value @@ -72,12 +85,12 @@ public struct ChangesOfModifier: AtomModifier { internal init(keyPath: KeyPath) { _keyPath = UnsafeUncheckedSendable(keyPath) } - #endif - /// A unique value used to identify the modifier internally. - public var key: Key { - Key(keyPath: keyPath) - } + /// A unique value used to identify the modifier internally. + public var key: Key { + Key(keyPath: _keyPath) + } + #endif /// A producer that produces the value of this atom. public func producer(atom: some Atom) -> AtomProducer { diff --git a/Sources/Atoms/Modifier/TaskPhaseModifier.swift b/Sources/Atoms/Modifier/TaskPhaseModifier.swift index 862284c7..5cf3608d 100644 --- a/Sources/Atoms/Modifier/TaskPhaseModifier.swift +++ b/Sources/Atoms/Modifier/TaskPhaseModifier.swift @@ -79,7 +79,7 @@ public struct TaskPhaseModifier: AsyncAtomMod public typealias Produced = AsyncPhase /// A type representing the stable identity of this atom associated with an instance. - public struct Key: Hashable {} + public struct Key: Hashable, Sendable {} /// A unique value used to identify the modifier internally. public var key: Key {