From 26d58bbc55a6ccc73dbaa4861ebc73f7c64e8aca Mon Sep 17 00:00:00 2001 From: Alexander Kauer Date: Thu, 30 Oct 2025 16:41:15 +0100 Subject: [PATCH 1/2] Added no content AsyncLoad --- README.md | 44 +++++++++++++++++++ .../AsyncLoad/AsyncLoad+NoContent.swift | 17 +++++++ .../AsyncLoad+NoContentTests.swift | 36 +++++++++++++++ .../AsyncLoadEquatableItemTests.swift | 2 + Tests/AsyncLoadTests/Utils.swift | 12 +++++ 5 files changed, 111 insertions(+) create mode 100644 Sources/AsyncLoad/AsyncLoad/AsyncLoad+NoContent.swift create mode 100644 Tests/AsyncLoadTests/AsyncLoad+NoContentTests.swift diff --git a/README.md b/README.md index 0415536..7f5aba8 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ A Swift package that provides elegant state management for asynchronous operatio AsyncLoad provides components for handling asynchronous operations: - `AsyncLoad`: For loading data operations +- `AsyncLoad` (without type): For operations that track loading state without data (uses `NoContent`) - `CachedAsyncLoad`: For loading operations that preserve cached data during refreshes - `AsyncLoadView`: A SwiftUI view component for displaying async states - `CachedAsyncLoadView`: A SwiftUI view component for cached async states @@ -80,6 +81,49 @@ class DataViewModel { } ``` +### AsyncLoad without Type Parameter (NoContent) + +For operations that need to track loading state but don't have any data to return, you can use `AsyncLoad` without a type parameter. This uses the `NoContent` type internally. + +```swift +public struct NoContent: Equatable, Sendable +public typealias AsyncLoadNoContent = AsyncLoad +``` + +This is useful for operations like: +- Delete operations +- Simple actions (e.g., mark as read, archive) +- Refresh operations without data +- Any operation where you only care about success/failure/loading state + +#### Example Usage + +```swift +import AsyncLoad + +@Observable +class ActionViewModel { + var deleteStatus: AsyncLoad = .none // No type parameter needed + + func deleteItem(id: String) async { + deleteStatus = .loading + + do { + try await itemService.deleteItem(id: id) + deleteStatus = .loaded // Convenience property for NoContent + } catch { + deleteStatus = .error(error) + } + } +} +``` + +You can also use the type alias explicitly: + +```swift +var deleteStatus: AsyncLoadNoContent = .none +``` + ### CachedAsyncLoad An enhanced version of AsyncLoad that preserves cached data during loading and error states. diff --git a/Sources/AsyncLoad/AsyncLoad/AsyncLoad+NoContent.swift b/Sources/AsyncLoad/AsyncLoad/AsyncLoad+NoContent.swift new file mode 100644 index 0000000..d857cef --- /dev/null +++ b/Sources/AsyncLoad/AsyncLoad/AsyncLoad+NoContent.swift @@ -0,0 +1,17 @@ +import Foundation + +public typealias AsyncLoadNoContent = AsyncLoad + +public struct NoContent: Equatable, Sendable { + public init() { } +} + +public extension AsyncLoad where T == NoContent { + init(_ type: AsyncLoad = .none) { + self = type + } + + static var loaded: AsyncLoad { + .loaded(NoContent()) + } +} diff --git a/Tests/AsyncLoadTests/AsyncLoad+NoContentTests.swift b/Tests/AsyncLoadTests/AsyncLoad+NoContentTests.swift new file mode 100644 index 0000000..b88f2b8 --- /dev/null +++ b/Tests/AsyncLoadTests/AsyncLoad+NoContentTests.swift @@ -0,0 +1,36 @@ +// +// AsyncLoad+NoContentTests.swift +// AsyncLoad +// +// Created by Alexander Kauer on 30.10.25. +// + +import Foundation +import Testing + +@Suite("Test no content AsyncLoad") +struct AsyncLoadNoContentTests { + + @Test<[AsyncLoadNoContentParameter]>("Should be equal", arguments: [ + .init(.none, .none), + .init(.loading, .loading), + .init(.loaded, .loaded), + .init(.error(TestingError.some), .error(TestingError.some)), + ]) + func noContentEqual(_ parameter: AsyncLoadNoContentParameter) { + #expect(parameter.load1 == parameter.load2) + } + + + @Test<[AsyncLoadNoContentParameter]>("Should not be equal", arguments: [ + .init(.none, .loading), + .init(.loading, .loaded), + .init(.loaded, .error(TestingError.some)), + .init(.error(TestingError.some), .none), + ]) + func noContentNotEqual(_ parameter: AsyncLoadNoContentParameter) { + #expect(parameter.load1 != parameter.load2) + } + + +} diff --git a/Tests/AsyncLoadTests/AsyncLoadEquatableItemTests.swift b/Tests/AsyncLoadTests/AsyncLoadEquatableItemTests.swift index d0a44fa..67c08f1 100644 --- a/Tests/AsyncLoadTests/AsyncLoadEquatableItemTests.swift +++ b/Tests/AsyncLoadTests/AsyncLoadEquatableItemTests.swift @@ -40,4 +40,6 @@ struct AsyncLoadEquatableItemTests { func nonEqualUser(param: AsyncLoadParameter) async throws { #expect(param.load1 != param.load2) } + + } diff --git a/Tests/AsyncLoadTests/Utils.swift b/Tests/AsyncLoadTests/Utils.swift index 7742343..fc05c45 100644 --- a/Tests/AsyncLoadTests/Utils.swift +++ b/Tests/AsyncLoadTests/Utils.swift @@ -28,6 +28,18 @@ struct AsyncLoadParameter { } } +let t = AsyncLoadNoContentParameter(.none, .none) + +struct AsyncLoadNoContentParameter { + let load1: AsyncLoadNoContent + let load2: AsyncLoadNoContent + + init(_ load1: AsyncLoadNoContent, _ load2: AsyncLoadNoContent) { + self.load1 = load1 + self.load2 = load2 + } +} + struct CachedAsyncLoadParameter { let load1: CachedAsyncLoad let load2: CachedAsyncLoad From d80efa000937f2b9ab1fe15db6d4197d504d5a82 Mon Sep 17 00:00:00 2001 From: Alexander Kauer Date: Thu, 30 Oct 2025 16:43:33 +0100 Subject: [PATCH 2/2] Cleanup --- Tests/AsyncLoadTests/AsyncLoad+NoContentTests.swift | 2 -- Tests/AsyncLoadTests/Utils.swift | 2 -- 2 files changed, 4 deletions(-) diff --git a/Tests/AsyncLoadTests/AsyncLoad+NoContentTests.swift b/Tests/AsyncLoadTests/AsyncLoad+NoContentTests.swift index b88f2b8..dbd3d3b 100644 --- a/Tests/AsyncLoadTests/AsyncLoad+NoContentTests.swift +++ b/Tests/AsyncLoadTests/AsyncLoad+NoContentTests.swift @@ -31,6 +31,4 @@ struct AsyncLoadNoContentTests { func noContentNotEqual(_ parameter: AsyncLoadNoContentParameter) { #expect(parameter.load1 != parameter.load2) } - - } diff --git a/Tests/AsyncLoadTests/Utils.swift b/Tests/AsyncLoadTests/Utils.swift index fc05c45..e19d616 100644 --- a/Tests/AsyncLoadTests/Utils.swift +++ b/Tests/AsyncLoadTests/Utils.swift @@ -28,8 +28,6 @@ struct AsyncLoadParameter { } } -let t = AsyncLoadNoContentParameter(.none, .none) - struct AsyncLoadNoContentParameter { let load1: AsyncLoadNoContent let load2: AsyncLoadNoContent