diff --git a/Sources/Toolbox/Utilities/SharedAsyncChannel.swift b/Sources/Toolbox/Utilities/SharedAsyncChannel.swift new file mode 100644 index 0000000..596860a --- /dev/null +++ b/Sources/Toolbox/Utilities/SharedAsyncChannel.swift @@ -0,0 +1,48 @@ +#if canImport(AsyncAlgorithms) +import AsyncAlgorithms +import Foundation + +public final class SharedAsyncChannel: AsyncSequence, @unchecked Sendable { + public typealias Element = Element + public typealias AsyncIterator = Iterator + private let lock = NSLock() + private var storage = [UUID: AsyncChannel]() + + public init() {} + + public func send(_ element: Element) async { + for channel in storage.values { + await channel.send(element) + } + } + + public func makeAsyncIterator() -> Iterator { + let channel = AsyncChannel() + let id = UUID() + lock.lock() + storage[id] = channel + lock.unlock() + return Iterator(innerIterator: channel.makeAsyncIterator(), parent: self, id: id) + } + + private func gotCancelled(id: UUID) { + lock.lock() + storage[id] = nil + lock.unlock() + } + + public struct Iterator: AsyncIteratorProtocol { + var innerIterator: AsyncChannel.AsyncIterator + let parent: SharedAsyncChannel + let id: UUID + + public mutating func next() async -> Element? { + return await withTaskCancellationHandler { + await innerIterator.next() + } onCancel: { [parent, id] in + parent.gotCancelled(id: id) + } + } + } +} +#endif diff --git a/Tests/ToolboxTests/TabBarCoordinatorTests.swift b/Tests/ToolboxTests/TabBarCoordinatorTests.swift index c415c36..f4537f1 100644 --- a/Tests/ToolboxTests/TabBarCoordinatorTests.swift +++ b/Tests/ToolboxTests/TabBarCoordinatorTests.swift @@ -10,7 +10,7 @@ final class TabBarCoordinatorTests: XCTestCase { // Switch to the main actor context. await MainActor.run { - let tabBarCoordinator = TabBarCoordinator() + let tabBarCoordinator = TabBarCoordinator(tabBarController: UITabBarController()) // Test your coordinator instance here, e.g.: XCTAssertNotNil(tabBarCoordinator)