Skip to content

Commit d8e39d5

Browse files
committed
Implement Feedback.autoconnect operator
- Implement a new `autoconnect` operator on `Feedback` that disposes and restores its "parent" Feedback observation according to a predicate on `State`.
1 parent 288a626 commit d8e39d5

File tree

5 files changed

+47
-41
lines changed

5 files changed

+47
-41
lines changed

Loop/Floodgate.swift

+1-13
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ final class Floodgate<State, Event>: FeedbackEventConsumer<Event> {
3434

3535
private let queue = Atomic(QueueState())
3636
private let reducer: (inout State, Event) -> Void
37-
private var feedbacks: [Loop<State, Event>.Feedback] = []
38-
private var feedbackDisposables = CompositeDisposable()
37+
private let feedbackDisposables = CompositeDisposable()
3938

4039
init(state: State, reducer: @escaping (inout State, Event) -> Void) {
4140
self.state = state
@@ -47,12 +46,6 @@ final class Floodgate<State, Event>: FeedbackEventConsumer<Event> {
4746
}
4847

4948
func bootstrap(with feedbacks: [Loop<State, Event>.Feedback]) {
50-
self.feedbacks = feedbacks
51-
52-
plugFeedbacks()
53-
}
54-
55-
func plugFeedbacks() {
5649
for feedback in feedbacks {
5750
// Pass `producer` which has replay-1 semantic.
5851
feedbackDisposables += feedback.events(
@@ -67,11 +60,6 @@ final class Floodgate<State, Event>: FeedbackEventConsumer<Event> {
6760
}
6861
}
6962

70-
func unplugFeedbacks() {
71-
feedbackDisposables.dispose()
72-
feedbackDisposables = CompositeDisposable()
73-
}
74-
7563
override func process(_ event: Event, for token: Token) {
7664
enqueue(event, for: token)
7765

Loop/LoopBox.swift

-20
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,6 @@ internal class ScopedLoopBox<RootState, RootEvent, ScopedState, ScopedEvent>: Lo
4444
event: { [eventTransform] in eventTransform(event($0)) }
4545
)
4646
}
47-
48-
override func pause() {
49-
root.pause()
50-
}
51-
52-
override func resume() {
53-
root.resume()
54-
}
5547
}
5648

5749
internal class RootLoopBox<State, Event>: LoopBoxBase<State, Event> {
@@ -91,14 +83,6 @@ internal class RootLoopBox<State, Event>: LoopBoxBase<State, Event> {
9183
ScopedLoopBox(root: self, value: scope, event: event)
9284
}
9385

94-
override func pause() {
95-
floodgate.unplugFeedbacks()
96-
}
97-
98-
override func resume() {
99-
floodgate.plugFeedbacks()
100-
}
101-
10286
func start(with feedbacks: [Loop<State, Event>.Feedback]) {
10387
floodgate.bootstrap(with: feedbacks + [input.feedback])
10488
}
@@ -131,10 +115,6 @@ internal class LoopBoxBase<State, Event> {
131115
) -> LoopBoxBase<S, E> {
132116
subclassMustImplement()
133117
}
134-
135-
func pause() { subclassMustImplement() }
136-
137-
func resume() { subclassMustImplement() }
138118
}
139119

140120
@inline(never)

Loop/Public/FeedbackLoop.swift

+11
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,17 @@ extension Loop {
453453
}
454454
}
455455

456+
public func autoconnect(followingChangesIn predicate: @escaping (State) -> Bool) -> Feedback {
457+
return Feedback { state, consumer in
458+
self.events(
459+
state
460+
.skipRepeats { predicate($0.0) == predicate($1.0) }
461+
.flatMap(.latest) { predicate($0.0) ? state.filter { predicate($0.0) } : .empty },
462+
consumer
463+
)
464+
}
465+
}
466+
456467
public static func combine(_ feedbacks: Loop<State, Event>.Feedback...) -> Feedback {
457468
return Feedback { state, consumer in
458469
feedbacks.map { feedback in

Loop/Public/Loop.swift

-8
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,6 @@ public final class Loop<State, Event> {
6969
box: box.scoped(to: { $0 }, event: { _ in fatalError() })
7070
)
7171
}
72-
73-
public func pause() {
74-
box.pause()
75-
}
76-
77-
public func resume() {
78-
box.resume()
79-
}
8072
}
8173

8274
extension Loop {

LoopTests/FeedbackVariantTests.swift

+35
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,39 @@ class FeedbackVariantTests: XCTestCase {
174174
loop.send("world")
175175
expect(hasCancelled) == true
176176
}
177+
178+
func test_autoconnect_disposes_and_restores_flows_according_to_predicate() {
179+
180+
let loop = Loop<String, String>(
181+
initial: "",
182+
reducer: { content, string in
183+
content = string
184+
},
185+
feedbacks: [
186+
Loop<String, String>.Feedback
187+
.init(
188+
lensing: { $0.hasPrefix("hello") ? $0 : nil },
189+
effects: { SignalProducer(value: $0.uppercased()) }
190+
)
191+
.autoconnect(followingChangesIn: { $0.contains("disconnect") == false })
192+
]
193+
)
194+
195+
expect(loop.box._current) == ""
196+
197+
// This should trigger an uppercased event
198+
loop.send("hello1")
199+
expect(loop.box._current) == "HELLO1"
200+
201+
loop.send("hello2")
202+
expect(loop.box._current) == "HELLO2"
203+
204+
// This should lead to feedabck "disconnection", which in turn should cancel and disable the uppercasing effect.
205+
loop.send("hello disconnect")
206+
expect(loop.box._current) == "hello disconnect"
207+
208+
// This should lead feedback "connection", which in turn should reestablish the uppercasing effect.
209+
loop.send("hello3")
210+
expect(loop.box._current) == "HELLO3"
211+
}
177212
}

0 commit comments

Comments
 (0)