From 64ffb801d0ef7a0a63c4b660b5d69df22d76dbdb Mon Sep 17 00:00:00 2001 From: Prachi Gauriar Date: Fri, 26 Sep 2025 11:23:21 -0400 Subject: [PATCH] Convert ContextualBusEventObserver.Dispatcher from a class into an actor --- .github/workflows/VerifyChanges.yaml | 3 +- CHANGELOG.md | 6 ++++ CLAUDE.md | 1 + .../ContextualBusEventObserver.swift | 28 +++++++++++-------- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/.github/workflows/VerifyChanges.yaml b/.github/workflows/VerifyChanges.yaml index e68617a..2baaa5c 100644 --- a/.github/workflows/VerifyChanges.yaml +++ b/.github/workflows/VerifyChanges.yaml @@ -56,8 +56,7 @@ jobs: steps: - name: Select Xcode 26.0.0 - run: | - sudo xcode-select -s /Applications/Xcode_26.0.0.app + run: sudo xcode-select -s /Applications/Xcode_26.0.0.app - name: Checkout uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6655da0..aa7b01b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # DevFoundation Changelog +## 1.3.0: TBD + + - We’ve updated the internal implementation of `ContextualEventBusObserver` to use an actor + instead of a class with a dispatch queue. This should have no impact on consumers. + + ## 1.2.0: September 24, 2025 This release introduces the `ObservableReference` type and updates `ExecutionGroup` to enable diff --git a/CLAUDE.md b/CLAUDE.md index 2c760b6..b87a92b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -91,6 +91,7 @@ A comprehensive utility library with the following major components: - **HashableByID**: Protocol for hashable-by-identity types - **IdentifiableBySelf**: Protocol for types identifiable by themselves - **JSONValue**: Unified JSON value representation + - **ObservableReference**: Reference wrapper for observable objects - **OptionalRepresentable**: Protocol for types with optional representations - **RetryPolicy**: Configurable retry behavior for operations - **SoftwareComponentID**: Identifier for software components diff --git a/Sources/DevFoundation/Event Bus/ContextualBusEventObserver.swift b/Sources/DevFoundation/Event Bus/ContextualBusEventObserver.swift index c505f1e..5694f07 100644 --- a/Sources/DevFoundation/Event Bus/ContextualBusEventObserver.swift +++ b/Sources/DevFoundation/Event Bus/ContextualBusEventObserver.swift @@ -118,7 +118,9 @@ public final class ContextualBusEventObserver: BusEventObserver where C return } - dispatcher.dispatch(event, to: handlers) + Task.immediate { + await dispatcher.dispatch(event, to: handlers) + } } @@ -136,7 +138,9 @@ public final class ContextualBusEventObserver: BusEventObserver where C return } - dispatcher.dispatch(event, to: handlers) + Task.immediate { + await dispatcher.dispatch(event, to: handlers) + } } } @@ -272,13 +276,12 @@ extension ContextualBusEventObserver { /// A class that stores context and dispatches events to handlers. - private final class Dispatcher: Sendable { + private actor Dispatcher { /// The shared context that can be mutated by handlers. - nonisolated(unsafe) - private var context: Context + private var context: Context - /// The dispatch queue that handlers are executed on. - private let queue = DispatchQueue( + /// The serial executor on which this actor’s jobs are executed. + private let serialExecutor = DispatchSerialQueue( label: reverseDNSPrefixed("contextual-bus-event-observer"), target: .utility ) @@ -298,11 +301,14 @@ extension ContextualBusEventObserver { /// - event: The event to handle. /// - handlers: The handlers that are registered to handle the event. func dispatch(_ event: Event, to handlers: [Handler]) { - queue.async { [self] in - for handler in handlers { - handler.handle(event, with: &context) - } + for handler in handlers { + handler.handle(event, with: &context) } } + + + nonisolated var unownedExecutor: UnownedSerialExecutor { + return .init(serialExecutor) + } } }