Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions Sources/Atoms/Atom/ModifiedAtom.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/// An atom type that applies a modifier to an atom.
///
/// Use ``Atom/modifier(_:)`` instead of using this atom directly.
public struct ModifiedAtom<Node: Atom, Modifier: AtomModifier>: Atom where Node.Hook.Value == Modifier.Value {
/// A type representing the stable identity of this atom.
public struct Key: Hashable {
private let atomKey: Node.Key
private let modifierKey: Modifier.Key

fileprivate init(
atomKey: Node.Key,
modifierKey: Modifier.Key
) {
self.atomKey = atomKey
self.modifierKey = modifierKey
}
}

private let atom: Node
private let modifier: Modifier

internal init(atom: Node, modifier: Modifier) {
self.atom = atom
self.modifier = modifier
}

/// A unique value used to identify the atom internally.
public var key: Key {
Key(atomKey: atom.key, modifierKey: modifier.key)
}

/// Internal use, the hook for managing the state of this atom.
public var hook: ModifiedHook<Node, Modifier> {
ModifiedHook(atom: atom, modifier: modifier)
}

/// Returns a boolean value that determines whether it should notify the value update to
/// watchers with comparing the given old value and the new value.
public func shouldNotifyUpdate(newValue: Modifier.ModifiedValue, oldValue: Modifier.ModifiedValue) -> Bool {
modifier.shouldNotifyUpdate(newValue: newValue, oldValue: oldValue)
}
}
9 changes: 5 additions & 4 deletions Sources/Atoms/Atoms.docc/Atoms.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ SwiftUI Atom Properties offers practical capabilities to manage the complexity o
- ``AsyncSequenceAtom``
- ``PublisherAtom``
- ``ObservableObjectAtom``
- ``ModifiedAtom``

### Modifiers

Expand Down Expand Up @@ -65,8 +66,9 @@ SwiftUI Atom Properties offers practical capabilities to manage the complexity o
### Internal System

- ``Atom``
- ``SelectModifierAtom``
- ``TaskPhaseModifierAtom``
- ``AtomModifier``
- ``SelectModifier``
- ``TaskPhaseModifier``
- ``AtomHook``
- ``AtomStateHook``
- ``AtomTaskHook``
Expand All @@ -79,5 +81,4 @@ SwiftUI Atom Properties offers practical capabilities to manage the complexity o
- ``AsyncSequenceHook``
- ``PublisherHook``
- ``ObservableObjectHook``
- ``SelectModifierHook``
- ``TaskPhaseModifierHook``
- ``ModifiedHook``
1 change: 1 addition & 0 deletions Sources/Atoms/Core/Hook/AtomHookContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public struct AtomHookContext<Coordinator> {
@usableFromInline
internal let _box: _AnyAtomHookContextBox

@usableFromInline
internal let coordinator: Coordinator

internal init<Node: Atom>(
Expand Down
34 changes: 34 additions & 0 deletions Sources/Atoms/Core/Hook/ModifiedHook.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/// Internal use, a hook type that determines behavioral details of corresponding atoms.
public struct ModifiedHook<Node: Atom, Modifier: AtomModifier>: AtomHook where Node.Hook.Value == Modifier.Value {
/// A reference type object to manage internal state.
public typealias Coordinator = Modifier.Coordinator

private let atom: Node
private let modifier: Modifier

internal init(atom: Node, modifier: Modifier) {
self.atom = atom
self.modifier = modifier
}

/// Creates a coordinator instance.
public func makeCoordinator() -> Coordinator {
modifier.makeCoordinator()
}

/// Gets and returns the observable object with the given context.
public func value(context: Context) -> Modifier.ModifiedValue {
modifier.get(context: context) ?? _assertingFallbackValue(context: context)
}

/// Instantiates and caches the observable object, and then subscribes to it.
public func update(context: Context) {
let value = context.atomContext.watch(atom)
modifier.update(context: context, with: value)
}

/// Overrides with the given observable object.
public func updateOverride(context: Context, with value: Modifier.ModifiedValue) {
modifier.set(value: value, context: context)
}
}
36 changes: 0 additions & 36 deletions Sources/Atoms/Core/Hook/SelectModifierHook.swift

This file was deleted.

47 changes: 0 additions & 47 deletions Sources/Atoms/Core/Hook/TaskPhaseModifierHook.swift

This file was deleted.

60 changes: 60 additions & 0 deletions Sources/Atoms/Modifier/AtomModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
public extension Atom {
/// Applies a modifier to an atom and returns a new atom.
///
/// - Parameter modifier: The modifier to apply to this atom.
/// - Returns: A new atom that is applied the given modifier.
func modifier<T: AtomModifier>(_ modifier: T) -> ModifiedAtom<Self, T> {
ModifiedAtom(atom: self, modifier: modifier)
}
}

/// A modifier that you apply to an atom, producing a different version
/// of the original value.
public protocol AtomModifier {
/// A type representing the stable identity of this modifier.
associatedtype Key: Hashable
associatedtype Coordinator
associatedtype Value
associatedtype ModifiedValue

/// A type of the context structure that to interact with internal store.
typealias Context = AtomHookContext<Coordinator>

/// A unique value used to identify the modifier internally.
var key: Key { get }

/// Returns a boolean value that determines whether it should notify the value update to
/// watchers with comparing the given old value and the new value.
///
/// - Parameters:
/// - newValue: The new value after update.
/// - oldValue: The old value before update.
///
/// - Returns: A boolean value that determines whether it should notify the value update
/// to watchers.
@MainActor
func shouldNotifyUpdate(newValue: ModifiedValue, oldValue: ModifiedValue) -> Bool

/// Creates a coordinator instance.
@MainActor
func makeCoordinator() -> Coordinator

/// Gets the value with the given context.
@MainActor
func get(context: Context) -> ModifiedValue?

/// Sets a value with the given context.
@MainActor
func set(value: ModifiedValue, context: Context)

/// Starts updating the current value by modifying the given value.
@MainActor
func update(context: Context, with value: Value)
}

public extension AtomModifier {
@MainActor
func shouldNotifyUpdate(newValue: ModifiedValue, oldValue: ModifiedValue) -> Bool {
true
}
}
69 changes: 41 additions & 28 deletions Sources/Atoms/Modifier/SelectModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,53 +25,66 @@ public extension Atom {
///
/// - Returns: An atom that provides the partial property of the original atom value.
@MainActor
func select<Value: Equatable>(
_ keyPath: KeyPath<Hook.Value, Value>
) -> SelectModifierAtom<Self, Value> {
SelectModifierAtom(base: self, keyPath: keyPath)
func select<Selected: Equatable>(
_ keyPath: KeyPath<Hook.Value, Selected>
) -> ModifiedAtom<Self, SelectModifier<Hook.Value, Selected>> {
modifier(SelectModifier(keyPath: keyPath))
}
}

/// An atom that selects the partial value of the specified key path from the original atom.
/// A modifier that selects the partial value of the specified key path
/// from the original atom.
///
/// You can also use ``Atom/select(_:)`` to constract this atom.
public struct SelectModifierAtom<Base: Atom, Value: Equatable>: Atom {
/// A type representing the stable identity of this atom associated with an instance.
/// Use ``Atom/select(_:)`` instead of using this modifier directly.
public struct SelectModifier<Value, Selected: Equatable>: AtomModifier {
/// A type representing the stable identity of this modifier.
public struct Key: Hashable {
private let base: Base.Key
private let keyPath: KeyPath<Base.Hook.Value, Value>
private let keyPath: KeyPath<Value, Selected>

fileprivate init(
base: Base.Key,
keyPath: KeyPath<Base.Hook.Value, Value>
) {
self.base = base
fileprivate init(keyPath: KeyPath<Value, Selected>) {
self.keyPath = keyPath
}
}

private let base: Base
private let keyPath: KeyPath<Base.Hook.Value, Value>
/// A reference type object to manage internal state.
public final class Coordinator {
internal var selected: Selected?
}

private let keyPath: KeyPath<Value, Selected>

/// Creates a new atom instance with given base atom and key path.
public init(base: Base, keyPath: KeyPath<Base.Hook.Value, Value>) {
self.base = base
internal init(keyPath: KeyPath<Value, Selected>) {
self.keyPath = keyPath
}

/// A unique value used to identify the atom internally.
/// A unique value used to identify the modifier internally.
public var key: Key {
Key(base: base.key, keyPath: keyPath)
}

/// The hook for managing the state of this atom internally.
public var hook: SelectModifierHook<Base, Value> {
Hook(base: base, keyPath: keyPath)
Key(keyPath: keyPath)
}

/// Returns a boolean value that determines whether it should notify the value update to
/// watchers with comparing the given old value and the new value.
public func shouldNotifyUpdate(newValue: Value, oldValue: Value) -> Bool {
public func shouldNotifyUpdate(newValue: Selected, oldValue: Selected) -> Bool {
newValue != oldValue
}

/// Creates a coordinator instance.
public func makeCoordinator() -> Coordinator {
Coordinator()
}

/// Gets the value with the given context.
public func get(context: Context) -> Selected? {
context.coordinator.selected
}

/// Sets a value with the given context.
public func set(value: Selected, context: Context) {
context.coordinator.selected = value
}

/// Update the current value by modifying the given value.
public func update(context: Context, with value: Value) {
set(value: value[keyPath: keyPath], context: context)
}
}
Loading