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
3 changes: 1 addition & 2 deletions .github/workflows/VerifyChanges.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 17 additions & 11 deletions Sources/DevFoundation/Event Bus/ContextualBusEventObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ public final class ContextualBusEventObserver<Context>: BusEventObserver where C
return
}

dispatcher.dispatch(event, to: handlers)
Task.immediate {
await dispatcher.dispatch(event, to: handlers)
}
}


Expand All @@ -136,7 +138,9 @@ public final class ContextualBusEventObserver<Context>: BusEventObserver where C
return
}

dispatcher.dispatch(event, to: handlers)
Task.immediate {
await dispatcher.dispatch(event, to: handlers)
}
}
}

Expand Down Expand Up @@ -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
)
Expand All @@ -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: Event, to handlers: [Handler<Event>]) {
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)
}
}
}