Skip to content

Conversation

mjohnson12
Copy link
Collaborator

@mjohnson12 mjohnson12 commented Jul 17, 2025

This is a breaking change to Workflow that removes ReactiveSwift from the public interface of Workflow and Workflow UI.
There are 3 changes:

  1. In WorkflowHost rendering is now a read only property of the latest rendering.
  2. In WorkflowHost renderingPublisher is a Combine publisher that publishes renderings. In searching the register code base there were very few places we were using a Signal/SignalProducer from the rendering property. The plan would be to change those consumers to use renderingPublisher and sink.
  3. In WorkflowHost output has been renamed to outputPublisher. It is now a Combine Publisher that can be used to subscribe to Workflow output. Per Andrew's suggestion I added a new protocol WorkflowOutputPubisher that exposes the outputPublisher. The output property is used in a lot of places in register. To fix those places I added an extension on WorkflowOutputPubisher in WorkflowReactiveSwift that re-exposes output as a Signal. All the places that use output will just need to import WorkflowReactiveSwift and they will continue to work.
  4. WorkflowHostingController in WorkflowUI now implements WorkflowOutputPublisher. Consumers using output will just need to import WorkflowReactiveSwift to continue to work.

Note: Even though this removes ReactiveSwift as a dependency from the Workflow and WorkflowUI targets the Workflow Package has to continue to have ReactiveSwift as a dependency since it's used by WorkflowReactiveSwift. But because register uses bazel if you import Workflow/WorkflowUI in your module it does not import (directly or transitively) ReactiveSwift.

Checklist

  • Unit Tests
  • UI Tests
  • Snapshot Tests (iOS only)
  • I have made corresponding changes to the documentation

@mjohnson12 mjohnson12 force-pushed the markj/reactive_swift_removal branch 8 times, most recently from bd84d2e to 13f4761 Compare July 17, 2025 18:11
@mjohnson12 mjohnson12 marked this pull request as ready for review July 31, 2025 14:19
@mjohnson12 mjohnson12 requested review from a team as code owners July 31, 2025 14:20
@mjohnson12 mjohnson12 changed the title Remove ReactiveSwift from Workflow public interface RF-9688 - Remove ReactiveSwift from Workflow public interface Jul 31, 2025
@mjohnson12 mjohnson12 force-pushed the markj/reactive_swift_removal branch 2 times, most recently from f7e5fc3 to 59bcc8b Compare August 19, 2025 19:03
Comment on lines 33 to 38
public protocol WorkflowOutputPublisher {
associatedtype Output

var outputPublisher: AnyPublisher<Output, Never> { get }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this protocol necessary? what's the benefit over just extending the concrete type directly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It allows us to add the output signal on both WorkflowHost and WorkflowHostingController by just extending the protocol in WorkflowReactiveSwift. This was a suggestion in Andrew's feedback.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah gotcha (sorry i missed the existing convo). so the tradeoff here is adding a 'public' protocol to which we really only want 2 specific things to conform so that we don't need to make Workflow or WorkflowUI depend on ReactiveSwift – is that right? mostly out of curiosity – is it possible to define the protocol with package visibility? that seems like it might be a slightly more accurate definition (assuming it works and integrates successfully into the monorepo).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before I had extensions on WorkflowHost and WorkflowHostingController which required a new module just to put the extension on WorkflowHostingController since I could not put it in WorkflowReactiveSwift since it does not know about WorkflowUI. Andrew's idea was use a protocol and drop the extension in WorkflowReactiveSwift so we don't need to have an additional module and the reactive swift stuff stays in WorkflowReactiveSwift.
I can look into the package visibility to see if that is possible.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I make the protocol package visibility then the output property can't be public:
Property cannot be declared public because its type uses a package type

Copy link
Contributor

@jamieQ jamieQ Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, makes sense – thanks for checking. maybe just as a matter of hygiene we should make the protocol underscored (_WorkflowOutputPublisher) and add comments regarding its purpose, since it doesn't seem like it's actually intended to be 'really' public.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

Copy link
Collaborator Author

@mjohnson12 mjohnson12 Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed up the added underscore.

@mjohnson12 mjohnson12 force-pushed the markj/reactive_swift_removal branch from 96bbd04 to 7ce7d31 Compare August 21, 2025 14:16
let rootNode: WorkflowNode<WorkflowType>

private let mutableRendering: MutableProperty<WorkflowType.Rendering>
private let renderingSubject: CurrentValueSubject<WorkflowType.Rendering, Never>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slightly inclined to split the storage for the property vs the observer, since that will give us the most control over when the 'outside world' sees the update. e.g.

var rendering: Rendering
let renderingSubject = PassThroughSubject<Rendering, Never>()

any thoughts on that? i guess one thing that would be different is that the old way (and presumably using a CVS) would have some baked-in synchronization mechanism over the underlying value. in theory this stuff should be main-thread-only though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We currently are using a ReactiveSwift MutableProperty which does have an internal lock. Yes CurrentValueSubject does have some baked in serialization mechanism albeit without Apple documentation on what that is. Since we can't enforce someone reading the rendering value on the main thread I'm inclined to use the CurrentValueSubject since it's about as close as we can get as a Combine version of what we have now.
If we split the storage for the property out we could always lock around getting/setting it so I'm not against doing that but I think CurrentValueSubject should work for how we are using it.

Comment on lines +6 to +25
public var output: Signal<Output, Never> {
Signal.unserialized { observer, lifetime in
let cancellable = outputPublisher.sink(
receiveCompletion: { completion in
switch completion {
case .finished:
observer.sendCompleted()

case .failure(let error):
observer.send(error: error)
}
},
receiveValue: { value in
observer.send(value: value)
}
)
lifetime.observeEnded {
cancellable.cancel()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's plan on testing this fairly thoroughly. i'm never certain what the exact lifetime semantics are for the ReactiveSwift stuff personally...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. This has been in use in the ReactiveSwift bridging module for a while.

Comment on lines 33 to 38
public protocol WorkflowOutputPublisher {
associatedtype Output

var outputPublisher: AnyPublisher<Output, Never> { get }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah gotcha (sorry i missed the existing convo). so the tradeoff here is adding a 'public' protocol to which we really only want 2 specific things to conform so that we don't need to make Workflow or WorkflowUI depend on ReactiveSwift – is that right? mostly out of curiosity – is it possible to define the protocol with package visibility? that seems like it might be a slightly more accurate definition (assuming it works and integrates successfully into the monorepo).

@jamieQ
Copy link
Contributor

jamieQ commented Aug 22, 2025

let's also include in the commit message something that clearly indicates this is a breaking change to the public API, a la the guidelines specified via 'conventional commits'

@mjohnson12 mjohnson12 force-pushed the markj/reactive_swift_removal branch from 7ce7d31 to 99238f0 Compare August 22, 2025 15:25
@mjohnson12 mjohnson12 changed the title RF-9688 - Remove ReactiveSwift from Workflow public interface RF-9688 - [refactor]: Remove ReactiveSwift from Workflow public interface Aug 22, 2025
@mjohnson12
Copy link
Collaborator Author

let's also include in the commit message something that clearly indicates this is a breaking change to the public API, a la the guidelines specified via 'conventional commits'

I've updated the commit message

@mjohnson12 mjohnson12 force-pushed the markj/reactive_swift_removal branch from 99238f0 to f9c40fd Compare August 25, 2025 17:01
@mjohnson12 mjohnson12 force-pushed the markj/reactive_swift_removal branch from f9c40fd to 2fee5aa Compare September 11, 2025 16:46
BREAKING CHANGE: This changes the public interface of WorkflowHost and WorkflowHostingController.
The rendering property is now a read only property of the Rendering
There is a new renderingPublisher property for a Combine publisher for renderings
The output Signal property has been removed and moved to an extension in WorkflowReactiveSwift
There is a new outputPublisher property for a Combine publisher for output
@mjohnson12 mjohnson12 force-pushed the markj/reactive_swift_removal branch from 2fee5aa to 101286a Compare September 30, 2025 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants