Skip to content

Commit 36e94e6

Browse files
committed
add more context to action handling call & add observation hook for state updates
1 parent b1b0a8c commit 36e94e6

File tree

4 files changed

+88
-12
lines changed

4 files changed

+88
-12
lines changed

Workflow/Sources/SubtreeManager.swift

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ extension WorkflowNode {
4747
}
4848

4949
/// Performs an update pass using the given closure.
50-
func render<Rendering>(_ actions: (RenderContext<WorkflowType>) -> Rendering) -> Rendering {
50+
func render<Rendering>(
51+
_ actions: (RenderContext<WorkflowType>) -> Rendering,
52+
workflow: WorkflowType
53+
) -> Rendering {
5154
/// Invalidate the previous action handlers.
5255
for eventPipe in eventPipes {
5356
eventPipe.invalidate()
@@ -58,6 +61,7 @@ extension WorkflowNode {
5861
previousSinks: previousSinks,
5962
originalChildWorkflows: childWorkflows,
6063
originalSideEffectLifetimes: sideEffectLifetimes,
64+
workflow: workflow,
6165
session: session,
6266
observer: observer
6367
)
@@ -146,14 +150,15 @@ extension WorkflowNode.SubtreeManager {
146150
private let originalSideEffectLifetimes: [AnyHashable: SideEffectLifetime]
147151
internal private(set) var usedSideEffectLifetimes: [AnyHashable: SideEffectLifetime]
148152

149-
private var session: WorkflowSession
150-
151-
private var observer: WorkflowObserver?
153+
private let workflow: WorkflowType
154+
private let session: WorkflowSession
155+
private let observer: WorkflowObserver?
152156

153157
internal init(
154158
previousSinks: [ObjectIdentifier: AnyReusableSink],
155159
originalChildWorkflows: [ChildKey: AnyChildWorkflow],
156160
originalSideEffectLifetimes: [AnyHashable: SideEffectLifetime],
161+
workflow: WorkflowType,
157162
session: WorkflowSession,
158163
observer: WorkflowObserver?
159164
) {
@@ -167,6 +172,7 @@ extension WorkflowNode.SubtreeManager {
167172
self.originalSideEffectLifetimes = originalSideEffectLifetimes
168173
self.usedSideEffectLifetimes = [:]
169174

175+
self.workflow = workflow
170176
self.session = session
171177
self.observer = observer
172178
}
@@ -231,13 +237,23 @@ extension WorkflowNode.SubtreeManager {
231237

232238
let signpostRef = SignpostRef()
233239

234-
let sink = Sink<Action> { [observer, session] action in
240+
// make the optional observer callback. doing this conditionally
241+
// means we can avoid capturing values in the Sink closure unless
242+
// they are actually needed.
243+
let notifyObserver = observer.map { observer in
244+
{ [observer, workflow, session] (action: Action) in
245+
observer.workflowDidReceiveAction(
246+
action,
247+
workflow: workflow,
248+
session: session
249+
)
250+
}
251+
}
252+
253+
let sink = Sink<Action> { action in
235254
WorkflowLogger.logSinkEvent(ref: signpostRef, action: action)
236255

237-
observer?.workflowDidReceiveAction(
238-
action,
239-
session: session
240-
)
256+
notifyObserver?(action)
241257

242258
reusableSink.handle(action: action)
243259
}

Workflow/Sources/WorkflowNode.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ final class WorkflowNode<WorkflowType: Workflow> {
8080

8181
switch subtreeOutput {
8282
case .update(let event, let source):
83+
let actionObserverCompletion = observer?.workflowWillApplyAction(
84+
event,
85+
workflow: workflow,
86+
state: state,
87+
session: session
88+
)
89+
8390
/// Apply the update to the current state
8491
let outputEvent = event.apply(toState: &state)
8592

@@ -92,6 +99,8 @@ final class WorkflowNode<WorkflowType: Workflow> {
9299
)
93100
)
94101

102+
actionObserverCompletion?(state, outputEvent)
103+
95104
case .childDidUpdate(let debugInfo):
96105
output = Output(
97106
outputEvent: nil,
@@ -127,13 +136,13 @@ final class WorkflowNode<WorkflowType: Workflow> {
127136
WorkflowLogger.logWorkflowFinishedRendering(ref: self, isRootNode: isRootNode)
128137
}
129138

130-
rendering = subtreeManager.render { context in
139+
rendering = subtreeManager.render({ context in
131140
workflow
132141
.render(
133142
state: state,
134143
context: context
135144
)
136-
}
145+
}, workflow: workflow)
137146

138147
return rendering
139148
}

Workflow/Sources/WorkflowObserver.swift

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,16 @@ public protocol WorkflowObserver {
7575
/// - session: The `WorkflowSession` corresponding to the backing `WorkflowNode`.
7676
func workflowDidReceiveAction<Action: WorkflowAction>(
7777
_ action: Action,
78+
workflow: Action.WorkflowType,
7879
session: WorkflowSession
7980
)
81+
82+
func workflowWillApplyAction<Action: WorkflowAction>(
83+
_ action: Action,
84+
workflow: Action.WorkflowType,
85+
state: Action.WorkflowType.State,
86+
session: WorkflowSession
87+
) -> ((Action.WorkflowType.State, Action.WorkflowType.Output?) -> Void)?
8088
}
8189

8290
// MARK: - WorkflowSession
@@ -171,8 +179,16 @@ public extension WorkflowObserver {
171179

172180
func workflowDidReceiveAction<Action: WorkflowAction>(
173181
_ action: Action,
182+
workflow: Action.WorkflowType,
174183
session: WorkflowSession
175184
) {}
185+
186+
func workflowWillApplyAction<Action: WorkflowAction>(
187+
_ action: Action,
188+
workflow: Action.WorkflowType,
189+
state: Action.WorkflowType.State,
190+
session: WorkflowSession
191+
) -> ((Action.WorkflowType.State, Action.WorkflowType.Output?) -> Void)? { nil }
176192
}
177193

178194
// MARK: Chained Observer
@@ -240,13 +256,40 @@ final class ChainedWorkflowObserver: WorkflowObserver {
240256
}
241257
}
242258

243-
func workflowDidReceiveAction<Action>(_ action: Action, session: WorkflowSession) where Action: WorkflowAction {
259+
func workflowDidReceiveAction<Action>(_ action: Action, workflow: Action.WorkflowType, session: WorkflowSession) where Action: WorkflowAction {
244260
for observer in observers {
245261
observer.workflowDidReceiveAction(
246262
action,
263+
workflow: workflow,
264+
session: session
265+
)
266+
}
267+
}
268+
269+
func workflowWillApplyAction<Action>(
270+
_ action: Action,
271+
workflow: Action.WorkflowType,
272+
state: Action.WorkflowType.State,
273+
session: WorkflowSession
274+
) -> ((Action.WorkflowType.State, Action.WorkflowType.Output?) -> Void)? where Action: WorkflowAction {
275+
let callbacks = observers.compactMap {
276+
$0.workflowWillApplyAction(
277+
action,
278+
workflow: workflow,
279+
state: state,
247280
session: session
248281
)
249282
}
283+
284+
guard !callbacks.isEmpty else {
285+
return nil
286+
}
287+
288+
return { state, output in
289+
for callback in callbacks.reversed() {
290+
callback(state, output)
291+
}
292+
}
250293
}
251294
}
252295

Workflow/Tests/SubtreeManagerTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,11 @@ private extension WorkflowNode.SubtreeManager {
338338
)
339339
}
340340
}
341+
342+
private extension WorkflowNode.SubtreeManager where WorkflowType == ParentWorkflow {
343+
func render<Rendering>(
344+
_ actions: (RenderContext<WorkflowType>) -> Rendering
345+
) -> Rendering {
346+
render(actions, workflow: ParentWorkflow())
347+
}
348+
}

0 commit comments

Comments
 (0)