From 012197bddd13e1b9e66dc9aca06a07d7c5d17ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=BCller?= Date: Sun, 8 Jun 2025 08:03:05 +0200 Subject: [PATCH] Add ergonomic proxies for LoadableSupport and ProcessSupport --- README.md | 20 ++--- .../Processed/Loadable/LoadableSupport.swift | 51 +++++++++++++ .../Processed/Process/ProcessSupport.swift | 74 +++++++++++++++++++ 3 files changed, 135 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 9526a1d..e1e8cc3 100644 --- a/README.md +++ b/README.md @@ -263,16 +263,16 @@ However, it's still really easy: You have to conform your class to the `Loadable @Published var numbers: LoadableState<[Int]> = .absent func loadNumbers() { - // Call the load method from the LoadableSupport protocol - load(\.numbers) { + // Use the new ergonomic accessor + loadables.numbers.load { try await Task.sleep(for: .seconds(2)) return [42] } } func loadStreamedNumbers() { - // Call the load method that yields results from the LoadableSupport protocol - load(\.numbers) { yield in + // Call the load method that yields results using the accessor + loadables.numbers.load { yield in var numbers: [Int] = [] for await number in [42, 73].publisher.values { try await Task.sleep(for: .seconds(1)) @@ -283,7 +283,7 @@ However, it's still really easy: You have to conform your class to the `Loadable } func cancelLoading() { - cancel(\.numbers) + loadables.numbers.cancel() } } ``` @@ -426,21 +426,21 @@ enum ProcessKind { @Published var process: Process = .idle func save() { - // Call the run method from the ProcessSupport protocol - run(\.process, as: .save) { + // Use the ergonomic accessor + processes.process.run(as: .save) { try await save() } } func delete() { - // Call the run method from the ProcessSupport protocol - run(\.process, as: .delete) { + // Use the ergonomic accessor + processes.process.run(as: .delete) { try await delete() } } func cancelLoading() { - cancel(\.process) + processes.process.cancel() } } ``` diff --git a/Sources/Processed/Loadable/LoadableSupport.swift b/Sources/Processed/Loadable/LoadableSupport.swift index d1027a4..0f196d4 100644 --- a/Sources/Processed/Loadable/LoadableSupport.swift +++ b/Sources/Processed/Loadable/LoadableSupport.swift @@ -548,3 +548,54 @@ extension LoadableSupport { } } } + +// MARK: - Ergonomic Accessors + +@dynamicMemberLookup +public struct Loadables { + unowned let container: Container + + public subscript(dynamicMember keyPath: ReferenceWritableKeyPath>) -> LoadableAccessor { + .init(container: container, keyPath: keyPath) + } +} + +public struct LoadableAccessor { + unowned let container: Container + let keyPath: ReferenceWritableKeyPath> + + @MainActor @discardableResult + public func load( + silently runSilently: Bool = false, + priority: TaskPriority? = nil, + @_implicitSelfCapture block: @MainActor @escaping () async throws -> Value + ) -> Task { + container.load(keyPath, silently: runSilently, priority: priority, block: block) + } + + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + @MainActor @discardableResult + public func load( + silently runSilently: Bool = false, + interrupts: [Duration], + priority: TaskPriority? = nil, + @_implicitSelfCapture block: @MainActor @escaping () async throws -> Value, + @_implicitSelfCapture onInterrupt: @MainActor @escaping (_ accumulatedDelay: Duration) throws -> Void + ) -> Task { + container.load(keyPath, silently: runSilently, interrupts: interrupts, priority: priority, block: block, onInterrupt: onInterrupt) + } + + @MainActor + public func cancel() { + container.cancel(keyPath) + } + + @MainActor + public func reset() { + container.reset(keyPath) + } +} + +public extension LoadableSupport { + var loadables: Loadables { .init(container: self) } +} diff --git a/Sources/Processed/Process/ProcessSupport.swift b/Sources/Processed/Process/ProcessSupport.swift index a9a550a..a1d63dc 100644 --- a/Sources/Processed/Process/ProcessSupport.swift +++ b/Sources/Processed/Process/ProcessSupport.swift @@ -555,3 +555,77 @@ extension ProcessSupport { } } } + +// MARK: - Ergonomic Accessors + +@dynamicMemberLookup +public struct Processes { + unowned let container: Container + + public subscript(dynamicMember keyPath: ReferenceWritableKeyPath>) -> ProcessAccessor { + .init(container: container, keyPath: keyPath) + } +} + +public struct ProcessAccessor { + unowned let container: Container + let keyPath: ReferenceWritableKeyPath> + + @MainActor @discardableResult + public func run( + as process: ProcessKind, + silently runSilently: Bool = false, + priority: TaskPriority? = nil, + @_implicitSelfCapture block: @MainActor @escaping () async throws -> Void + ) -> Task { + container.run(keyPath, as: process, silently: runSilently, priority: priority, block: block) + } + + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + @MainActor @discardableResult + public func run( + as process: ProcessKind, + silently runSilently: Bool = false, + interrupts: [Duration], + priority: TaskPriority? = nil, + @_implicitSelfCapture block: @MainActor @escaping () async throws -> Void, + @_implicitSelfCapture onInterrupt: @MainActor @escaping (_ accumulatedDelay: Duration) throws -> Void + ) -> Task { + container.run(keyPath, as: process, silently: runSilently, interrupts: interrupts, priority: priority, block: block, onInterrupt: onInterrupt) + } + + @MainActor @discardableResult + public func run( + silently runSilently: Bool = false, + priority: TaskPriority? = nil, + @_implicitSelfCapture block: @MainActor @escaping () async throws -> Void + ) -> Task where ProcessKind == SingleProcess { + container.run(keyPath, silently: runSilently, priority: priority, block: block) + } + + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + @MainActor @discardableResult + public func run( + silently runSilently: Bool = false, + interrupts: [Duration], + priority: TaskPriority? = nil, + @_implicitSelfCapture block: @MainActor @escaping () async throws -> Void, + @_implicitSelfCapture onInterrupt: @MainActor @escaping (_ accumulatedDelay: Duration) throws -> Void + ) -> Task where ProcessKind == SingleProcess { + container.run(keyPath, silently: runSilently, interrupts: interrupts, priority: priority, block: block, onInterrupt: onInterrupt) + } + + @MainActor + public func cancel() { + container.cancel(keyPath) + } + + @MainActor + public func reset() { + container.reset(keyPath) + } +} + +public extension ProcessSupport { + var processes: Processes { .init(container: self) } +}