Skip to content

Commit bd84d2e

Browse files
committed
Remove ReactiveSwift from Workflow public interface
1 parent ff4aaea commit bd84d2e

23 files changed

+387
-116
lines changed

Package.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ let package = Package(
2424
.singleTargetLibrary("WorkflowUI"),
2525
.singleTargetLibrary("WorkflowSwiftUI"),
2626

27+
// MARK: WorkflowUIReactiveSwift
28+
29+
.singleTargetLibrary("WorkflowUIReactiveSwift"),
30+
2731
// MARK: WorkflowReactiveSwift
2832

2933
.singleTargetLibrary("WorkflowReactiveSwift"),
@@ -68,12 +72,11 @@ let package = Package(
6872

6973
.target(
7074
name: "Workflow",
71-
dependencies: ["ReactiveSwift"],
7275
path: "Workflow/Sources"
7376
),
7477
.testTarget(
7578
name: "WorkflowTests",
76-
dependencies: ["Workflow"],
79+
dependencies: ["ReactiveSwift", "Workflow"],
7780
path: "Workflow/Tests"
7881
),
7982
.target(
@@ -104,6 +107,19 @@ let package = Package(
104107
path: "WorkflowUI/Tests"
105108
),
106109

110+
// MARK: WorkflowUIReactiveSwift
111+
112+
.target(
113+
name: "WorkflowUIReactiveSwift",
114+
dependencies: ["ReactiveSwift", "Workflow", "WorkflowUI", "ViewEnvironment", "ViewEnvironmentUI"],
115+
path: "WorkflowUIReactiveSwift/Sources"
116+
),
117+
.testTarget(
118+
name: "WorkflowUIReactiveSwiftTests",
119+
dependencies: ["WorkflowUIReactiveSwift", "WorkflowReactiveSwift"],
120+
path: "WorkflowUIReactiveSwift/Tests"
121+
),
122+
107123
// MARK: WorkflowSwiftUI
108124

109125
.target(

Samples/Project.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,10 @@ let project = Project(
159159
.unitTest(
160160
for: "Workflow",
161161
sources: "../Workflow/Tests/**",
162-
dependencies: [.external(name: "Workflow")]
162+
dependencies: [
163+
.external(name: "ReactiveSwift"),
164+
.external(name: "Workflow"),
165+
]
163166
),
164167
.unitTest(
165168
for: "WorkflowTesting",
@@ -252,6 +255,17 @@ let project = Project(
252255
.target(name: "TestAppHost"),
253256
]
254257
),
258+
259+
.unitTest(
260+
for: "WorkflowUIReactiveSwift",
261+
sources: "../WorkflowUIReactiveSwift/Tests/**",
262+
dependencies: [
263+
.external(name: "WorkflowUIReactiveSwift"),
264+
.external(name: "WorkflowUI"),
265+
.external(name: "WorkflowReactiveSwift"),
266+
.target(name: "TestAppHost"),
267+
]
268+
),
255269
],
256270
schemes: [
257271
.scheme(

Samples/Workspace.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ let workspace = Workspace(
99
.workflow("Workflow"),
1010
.workflow("WorkflowTesting"),
1111
.workflow("WorkflowUI"),
12+
.workflow("WorkflowUIReactiveSwift"),
1213
.workflow("WorkflowSwiftUI"),
1314
.workflow("WorkflowSwiftUIMacros"),
1415
.workflow("WorkflowReactiveSwift"),
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Combine
2+
3+
public struct ReadOnlyCurrentValueSubject<Output, Failure>: Combine.Publisher where Failure: Error {
4+
private let currentValueSubject: CurrentValueSubject<Output, Failure>
5+
6+
public var value: Output {
7+
currentValueSubject.value
8+
}
9+
10+
private init(_ value: Output) {
11+
self.currentValueSubject = CurrentValueSubject<Output, Failure>(value)
12+
}
13+
14+
public static func publisher(value: Output) -> (Self, CurrentValueSubject<Output, Failure>) {
15+
let publisher = Self(value)
16+
return (publisher, publisher.currentValueSubject)
17+
}
18+
19+
public func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input {
20+
currentValueSubject.receive(subscriber: subscriber)
21+
}
22+
}

Workflow/Sources/WorkflowHost.swift

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import ReactiveSwift
17+
import Combine
1818

1919
/// Defines a type that receives debug information about a running workflow hierarchy.
2020
public protocol WorkflowDebugger {
@@ -32,16 +32,15 @@ public protocol WorkflowDebugger {
3232

3333
/// Manages an active workflow hierarchy.
3434
public final class WorkflowHost<WorkflowType: Workflow> {
35-
private let (outputEvent, outputEventObserver) = Signal<WorkflowType.Output, Never>.pipe()
36-
3735
// @testable
3836
let rootNode: WorkflowNode<WorkflowType>
3937

40-
private let mutableRendering: MutableProperty<WorkflowType.Rendering>
38+
private let muableRendering: CurrentValueSubject<WorkflowType.Rendering, Never>
39+
private let outputSubject = PassthroughSubject<WorkflowType.Output, Never>()
4140

4241
/// Represents the `Rendering` produced by the root workflow in the hierarchy. New `Rendering` values are produced
4342
/// as state transitions occur within the hierarchy.
44-
public let rendering: Property<WorkflowType.Rendering>
43+
public let rendering: ReadOnlyCurrentValueSubject<WorkflowType.Rendering, Never>
4544

4645
/// Context object to pass down to descendant nodes in the tree.
4746
let context: HostContext
@@ -77,8 +76,8 @@ public final class WorkflowHost<WorkflowType: Workflow> {
7776
parentSession: nil
7877
)
7978

80-
self.mutableRendering = MutableProperty(rootNode.render())
81-
self.rendering = Property(mutableRendering)
79+
(self.rendering, self.muableRendering) = ReadOnlyCurrentValueSubject.publisher(value: rootNode.render())
80+
8281
rootNode.enableEvents()
8382

8483
debugger?.didEnterInitialState(snapshot: rootNode.makeDebugSnapshot())
@@ -106,10 +105,10 @@ public final class WorkflowHost<WorkflowType: Workflow> {
106105
}
107106

108107
private func handle(output: WorkflowNode<WorkflowType>.Output) {
109-
mutableRendering.value = rootNode.render()
108+
muableRendering.send(rootNode.render())
110109

111110
if let outputEvent = output.outputEvent {
112-
outputEventObserver.send(value: outputEvent)
111+
outputSubject.send(outputEvent)
113112
}
114113

115114
debugger?.didUpdate(
@@ -120,9 +119,9 @@ public final class WorkflowHost<WorkflowType: Workflow> {
120119
rootNode.enableEvents()
121120
}
122121

123-
/// A signal containing output events emitted by the root workflow in the hierarchy.
124-
public var output: Signal<WorkflowType.Output, Never> {
125-
outputEvent
122+
/// A publisher containing output events emitted by the root workflow in the hierarchy.
123+
public var outputPublisher: AnyPublisher<WorkflowType.Output, Never> {
124+
outputSubject.eraseToAnyPublisher()
126125
}
127126
}
128127

Workflow/Tests/AnyWorkflowTests.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import ReactiveSwift
17+
import Combine
1818
import XCTest
1919
@testable import Workflow
2020

@@ -40,19 +40,21 @@ public class AnyWorkflowTests: XCTestCase {
4040
let host = WorkflowHost(workflow: OnOutputWorkflow())
4141

4242
let renderingExpectation = expectation(description: "Waiting for rendering")
43-
host.rendering.producer.startWithValues { rendering in
43+
let cancellable = host.rendering.sink { rendering in
4444
if rendering {
4545
renderingExpectation.fulfill()
4646
}
4747
}
4848

4949
let outputExpectation = expectation(description: "Waiting for output")
50-
host.output.observeValues { output in
50+
let outputCancellable = host.outputPublisher.sink { output in
5151
if output {
5252
outputExpectation.fulfill()
5353
}
5454
}
5555
wait(for: [renderingExpectation, outputExpectation], timeout: 1)
56+
cancellable.cancel()
57+
outputCancellable.cancel()
5658
}
5759

5860
func testOnlyWrapsOnce() {

Workflow/Tests/ConcurrencyTests.swift

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ final class ConcurrencyTests: XCTestCase {
2929
var first = true
3030
var observedScreen: TestScreen?
3131

32-
let disposable = host.rendering.signal.observeValues { rendering in
32+
let cancellable = host.rendering.sink { rendering in
3333
if first {
3434
expectation.fulfill()
3535
first = false
@@ -47,12 +47,10 @@ final class ConcurrencyTests: XCTestCase {
4747
wait(for: [expectation], timeout: 1.0)
4848
guard let screen = observedScreen else {
4949
XCTFail("Screen was not updated.")
50-
disposable?.dispose()
5150
return
5251
}
5352
XCTAssertEqual(1, screen.count)
54-
55-
disposable?.dispose()
53+
cancellable.cancel()
5654
}
5755

5856
// Events emitted between `render` on a workflow and `enableEvents` are queued and will be delivered asynchronously after rendering is updated.
@@ -62,7 +60,7 @@ final class ConcurrencyTests: XCTestCase {
6260
let renderingExpectation = expectation(description: "Waiting on rendering values.")
6361
var first = true
6462

65-
let disposable = host.rendering.signal.observeValues { rendering in
63+
let cancellable = host.rendering.dropFirst().sink { rendering in
6664
if first {
6765
first = false
6866
// Emit an event when the rendering is first received.
@@ -81,8 +79,7 @@ final class ConcurrencyTests: XCTestCase {
8179
waitForExpectations(timeout: 1)
8280

8381
XCTAssertEqual(2, host.rendering.value.count)
84-
85-
disposable?.dispose()
82+
cancellable.cancel()
8683
}
8784

8885
func test_multipleQueuedEvents() {
@@ -91,7 +88,7 @@ final class ConcurrencyTests: XCTestCase {
9188
let renderingExpectation = expectation(description: "Waiting on rendering values.")
9289
var renderingValuesCount = 0
9390

94-
let disposable = host.rendering.signal.observeValues { rendering in
91+
let cancellable = host.rendering.dropFirst().sink { rendering in
9592
if renderingValuesCount == 0 {
9693
// Emit two events.
9794
rendering.update()
@@ -116,8 +113,7 @@ final class ConcurrencyTests: XCTestCase {
116113
waitForExpectations(timeout: 1)
117114

118115
XCTAssertEqual(3, host.rendering.value.count)
119-
120-
disposable?.dispose()
116+
cancellable.cancel()
121117
}
122118

123119
// A `sink` is invalidated after a single action has been received. However, if the next `render` pass uses a sink
@@ -232,7 +228,7 @@ final class ConcurrencyTests: XCTestCase {
232228
var first = true
233229

234230
let renderingsComplete = expectation(description: "Waiting for renderings")
235-
let disposable = host.rendering.signal.observeValues { rendering in
231+
let cancellable = host.rendering.dropFirst().sink { rendering in
236232
if first {
237233
first = false
238234
rendering.update()
@@ -249,8 +245,7 @@ final class ConcurrencyTests: XCTestCase {
249245
XCTAssertEqual(2, debugger.snapshots.count)
250246
XCTAssertEqual("1", debugger.snapshots[0].stateDescription)
251247
XCTAssertEqual("2", debugger.snapshots[1].stateDescription)
252-
253-
disposable?.dispose()
248+
cancellable.cancel()
254249
}
255250

256251
func test_childWorkflowsAreSynchronous() {
@@ -318,11 +313,11 @@ final class ConcurrencyTests: XCTestCase {
318313

319314
let renderingExpectation = XCTestExpectation()
320315
let outputExpectation = XCTestExpectation()
321-
let outDisposable = host.output.signal.observeValues { output in
316+
let outputCancellable = host.outputPublisher.sink { output in
322317
outputExpectation.fulfill()
323318
}
324319

325-
let disposable = host.rendering.signal.observeValues { rendering in
320+
let cancellable = host.rendering.sink { rendering in
326321
renderingExpectation.fulfill()
327322
}
328323

@@ -337,8 +332,8 @@ final class ConcurrencyTests: XCTestCase {
337332

338333
XCTAssertEqual(101, host.rendering.value.count)
339334

340-
disposable?.dispose()
341-
outDisposable?.dispose()
335+
cancellable.cancel()
336+
outputCancellable.cancel()
342337
}
343338

344339
// Since event pipes are reused for the same type, validate that the `AnyWorkflowAction`

Workflow/Tests/StateMutationSinkTests.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17+
import Combine
1718
import ReactiveSwift
1819
import Workflow
1920
import XCTest
@@ -35,14 +36,15 @@ final class StateMutationSinkTests: XCTestCase {
3536
let host = WorkflowHost(workflow: TestWorkflow(value: 100, signal: output))
3637

3738
let gotValueExpectation = expectation(description: "Got expected value")
38-
host.rendering.producer.startWithValues { val in
39+
let cancellable = host.rendering.sink { val in
3940
if val == 100 {
4041
gotValueExpectation.fulfill()
4142
}
4243
}
4344

4445
input.send(value: 100)
4546
waitForExpectations(timeout: 1, handler: nil)
47+
cancellable.cancel()
4648
}
4749

4850
func test_multipleUpdates() {
@@ -51,7 +53,7 @@ final class StateMutationSinkTests: XCTestCase {
5153
let gotValueExpectation = expectation(description: "Got expected value")
5254

5355
var values: [Int] = []
54-
host.rendering.producer.startWithValues { val in
56+
let cancellable = host.rendering.sink { val in
5557
values.append(val)
5658
if val == 300 {
5759
gotValueExpectation.fulfill()
@@ -63,6 +65,7 @@ final class StateMutationSinkTests: XCTestCase {
6365
input.send(value: 300)
6466
XCTAssertEqual(values, [0, 100, 200, 300])
6567
waitForExpectations(timeout: 1, handler: nil)
68+
cancellable.cancel()
6669
}
6770

6871
fileprivate struct TestWorkflow: Workflow {

Workflow/Tests/SubtreeManagerTests.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
* limitations under the License.
1515
*/
1616

17-
import ReactiveSwift
1817
import XCTest
1918
@testable import Workflow
2019

Workflow/Tests/WorkflowHostTests.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ final class WorkflowHost_EventEmissionTests: XCTestCase {
6565

6666
XCTAssertEqual(initialRendering.eventCount, 0)
6767

68-
let disposable = host.rendering.signal.observeValues { rendering in
68+
let cancellable = host.rendering.dropFirst().sink { rendering in
6969
XCTAssertEqual(rendering.eventCount, 1)
7070

7171
// emit another event using an old rendering
@@ -78,7 +78,8 @@ final class WorkflowHost_EventEmissionTests: XCTestCase {
7878

7979
observedRenderCount += 1
8080
}
81-
defer { disposable?.dispose() }
81+
82+
defer { cancellable.cancel() }
8283

8384
// send an event and cause a re-render
8485
initialRendering.eventHandler()

0 commit comments

Comments
 (0)