Skip to content

Commit

Permalink
Address Reducer._printChanges() sendability (#3320)
Browse files Browse the repository at this point in the history
* Address `Reducer._printChanges()` sendability

Because printing is done on a queue, both `State` and `Action` must be
sendable. While `State` is easy enough to make sendable, it might be a
pain to do so in a large, modularized application. Actions are not
always so easy, but are in simple cases.

Alternately, since this is a debugging affordance:

1. We could forego sendability since all we're doing is hitting it with
   a `Mirror` at the end of the day, and traffic the state/action along
   in a `nonisolated(unsafe)`.

2. We could ditch the queue...but that could affect the performance
   pretty negatively in some cases.

* Unchecked send the debug printing
  • Loading branch information
stephencelis authored Aug 30, 2024
1 parent 39268ea commit 40c5fb4
Showing 1 changed file with 20 additions and 6 deletions.
26 changes: 20 additions & 6 deletions Sources/ComposableArchitecture/Reducer/Reducers/DebugReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,21 @@ extension Reducer {

private let printQueue = DispatchQueue(label: "co.pointfree.swift-composable-architecture.printer")

public struct _ReducerPrinter<State, Action> {
private let _printChange: (_ receivedAction: Action, _ oldState: State, _ newState: State) -> Void
public struct _ReducerPrinter<State, Action>: Sendable {
private let _printChange: @Sendable (
_ receivedAction: Action,
_ oldState: State,
_ newState: State
) -> Void
@usableFromInline
let queue: DispatchQueue

public init(
printChange: @escaping (_ receivedAction: Action, _ oldState: State, _ newState: State) -> Void,
printChange: @escaping @Sendable (
_ receivedAction: Action,
_ oldState: State,
_ newState: State
) -> Void,
queue: DispatchQueue? = nil
) {
self._printChange = printChange
Expand Down Expand Up @@ -77,17 +85,23 @@ public struct _PrintChangesReducer<Base: Reducer>: Reducer {
) -> Effect<Base.Action> {
if let printer = self.printer {
return withSharedChangeTracking { changeTracker in
let oldState = state
let oldState = UncheckedSendable(state)
let effects = self.base.reduce(into: &state, action: action)
return withEscapedDependencies { continuation in
effects.merge(
with: .publisher { [newState = state, queue = printer.queue] in
with: .publisher { [
newState = UncheckedSendable(state),
action = UncheckedSendable(action),
queue = printer.queue
] in
Deferred<Empty<Action, Never>> {
queue.async {
continuation.yield {
changeTracker.assert {
printer.printChange(
receivedAction: action, oldState: oldState, newState: newState
receivedAction: action.wrappedValue,
oldState: oldState.wrappedValue,
newState: newState.wrappedValue
)
}
}
Expand Down

0 comments on commit 40c5fb4

Please sign in to comment.