Skip to content

Commit c141325

Browse files
committed
Updates Feedbacks.
1 parent 5a63b30 commit c141325

File tree

2 files changed

+147
-97
lines changed

2 files changed

+147
-97
lines changed

RxExample/RxExample/Examples/GitHubSearchRepositories/GitHubSearchRepositories.swift

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,31 +71,26 @@ func githubSearchRepositories(
7171
performSearch: @escaping (URL) -> Observable<SearchRepositoriesResponse>
7272
) -> Driver<GitHubSearchRepositoriesState> {
7373

74-
let searchPerformerFeedback: (Driver<GitHubSearchRepositoriesState>) -> Signal<GitHubCommand> = { state in
75-
// this is a general pattern how to model a most common feedback loop
76-
// first select part of state describing feedback control
77-
return state.map { (searchText: $0.searchText, shouldLoadNextPage: $0.shouldLoadNextPage, nextURL: $0.nextURL) }
78-
// only propagate changed control values since there could be multiple feedback loops working in parallel
79-
.distinctUntilChanged { $0 == $1 }
80-
// perform feedback loop effects
81-
.flatMapLatest { value -> Signal<GitHubCommand> in
82-
if !value.shouldLoadNextPage {
74+
let searchPerformerFeedback: (Driver<GitHubSearchRepositoriesState>) -> Signal<GitHubCommand> = react(
75+
query: { (searchText: $0.searchText, shouldLoadNextPage: $0.shouldLoadNextPage, nextURL: $0.nextURL) },
76+
effects: { query -> Signal<GitHubCommand> in
77+
if !query.shouldLoadNextPage {
8378
return Signal.empty()
8479
}
8580

86-
if value.searchText.isEmpty {
81+
if query.searchText.isEmpty {
8782
return Signal.just(GitHubCommand.gitHubResponseReceived(.success((repositories: [], nextURL: nil))))
8883
}
8984

90-
guard let nextURL = value.nextURL else {
85+
guard let nextURL = query.nextURL else {
9186
return Signal.empty()
9287
}
9388

9489
return performSearch(nextURL)
9590
.asSignal(onErrorJustReturn: .failure(GitHubServiceError.networkError))
9691
.map(GitHubCommand.gitHubResponseReceived)
9792
}
98-
}
93+
)
9994

10095
// this is degenerated feedback loop that doesn't depend on output state
10196
let inputFeedbackLoop: (Driver<GitHubSearchRepositoriesState>) -> Signal<GitHubCommand> = { state in

RxExample/RxExample/Feedbacks.swift

Lines changed: 140 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,35 @@ import RxCocoa
1212
// Taken from RxFeedback repo
1313

1414
/**
15-
Control feedback loop that tries to immediatelly perform the latest required effect.
16-
1715
* State: State type of the system.
18-
* Control: Subset of state used to control the feedback loop.
16+
* Query: Subset of state used to control the feedback loop.
1917

20-
When query result exists (not `nil`), feedback loop is active and it performs effects.
18+
When `query` returns a value, that value is being passed into `effects` lambda to decide which effects should be performed.
19+
In case new `query` is different from the previous one, new effects are calculated by using `effects` lambda and then performed.
2120

22-
When query result is `nil`, feedback loops doesn't perform any effect.
21+
When `query` returns `nil`, feedback loops doesn't perform any effect.
2322

24-
- parameter query: State type of the system
25-
- parameter effects: Control state which is subset of state.
23+
- parameter query: Part of state that controls feedback loop.
24+
- parameter areEqual: Part of state that controls feedback loop.
25+
- parameter effects: Chooses which effects to perform for certain query result.
2626
- returns: Feedback loop performing the effects.
2727
*/
28-
public func react<State, Control: Equatable, Event>(
29-
query: @escaping (State) -> Control?,
30-
effects: @escaping (Control) -> Observable<Event>
28+
public func react<State, Query, Event>(
29+
query: @escaping (State) -> Query?,
30+
areEqual: @escaping (Query, Query) -> Bool,
31+
effects: @escaping (Query) -> Observable<Event>
3132
) -> (ObservableSchedulerContext<State>) -> Observable<Event> {
3233
return { state in
3334
return state.map(query)
34-
.distinctUntilChanged { $0 == $1 }
35-
.flatMapLatest { (control: Control?) -> Observable<Event> in
35+
.distinctUntilChanged { lhs, rhs in
36+
switch (lhs, rhs) {
37+
case (.none, .none): return true
38+
case (.none, .some): return false
39+
case (.some, .none): return false
40+
case (.some(let lhs), .some(let rhs)): return areEqual(lhs, rhs)
41+
}
42+
}
43+
.flatMapLatest { (control: Query?) -> Observable<Event> in
3644
guard let control = control else {
3745
return Observable<Event>.empty()
3846
}
@@ -44,55 +52,102 @@ public func react<State, Control: Equatable, Event>(
4452
}
4553

4654
/**
47-
Control feedback loop that tries to immediatelly perform the latest required effect.
55+
* State: State type of the system.
56+
* Query: Subset of state used to control the feedback loop.
57+
58+
When `query` returns a value, that value is being passed into `effects` lambda to decide which effects should be performed.
59+
In case new `query` is different from the previous one, new effects are calculated by using `effects` lambda and then performed.
60+
61+
When `query` returns `nil`, feedback loops doesn't perform any effect.
62+
63+
- parameter query: Part of state that controls feedback loop.
64+
- parameter effects: Chooses which effects to perform for certain query result.
65+
- returns: Feedback loop performing the effects.
66+
*/
67+
public func react<State, Query: Equatable, Event>(
68+
query: @escaping (State) -> Query?,
69+
effects: @escaping (Query) -> Observable<Event>
70+
) -> (ObservableSchedulerContext<State>) -> Observable<Event> {
71+
return react(query: query, areEqual: { $0 == $1 }, effects: effects)
72+
}
4873

74+
/**
4975
* State: State type of the system.
50-
* Control: Subset of state used to control the feedback loop.
76+
* Query: Subset of state used to control the feedback loop.
5177

52-
When query result exists (not `nil`), feedback loop is active and it performs effects.
78+
When `query` returns a value, that value is being passed into `effects` lambda to decide which effects should be performed.
79+
In case new `query` is different from the previous one, new effects are calculated by using `effects` lambda and then performed.
5380

54-
When query result is `nil`, feedback loops doesn't perform any effect.
81+
When `query` returns `nil`, feedback loops doesn't perform any effect.
5582

56-
- parameter query: State type of the system
57-
- parameter effects: Control state which is subset of state.
83+
- parameter query: Part of state that controls feedback loop.
84+
- parameter areEqual: Part of state that controls feedback loop.
85+
- parameter effects: Chooses which effects to perform for certain query result.
5886
- returns: Feedback loop performing the effects.
5987
*/
60-
public func react<State, Control: Equatable, Event>(
61-
query: @escaping (State) -> Control?,
62-
effects: @escaping (Control) -> Signal<Event>
63-
) -> (Driver<State>) -> Signal<Event> {
88+
public func react<State, Query, Event>(
89+
query: @escaping (State) -> Query?,
90+
areEqual: @escaping (Query, Query) -> Bool,
91+
effects: @escaping (Query) -> Signal<Event>
92+
) -> (Driver<State>) -> Signal<Event> {
6493
return { state in
6594
let observableSchedulerContext = ObservableSchedulerContext<State>(
6695
source: state.asObservable(),
6796
scheduler: Signal<Event>.SharingStrategy.scheduler.async
6897
)
69-
return react(query: query, effects: { effects($0).asObservable() })(observableSchedulerContext)
98+
return react(query: query, areEqual: areEqual, effects: { effects($0).asObservable() })(observableSchedulerContext)
7099
.asSignal(onErrorSignalWith: .empty())
71100
}
72101
}
73102

74103
/**
75-
Control feedback loop that tries to immediatelly perform the latest required effect.
104+
* State: State type of the system.
105+
* Query: Subset of state used to control the feedback loop.
106+
107+
When `query` returns a value, that value is being passed into `effects` lambda to decide which effects should be performed.
108+
In case new `query` is different from the previous one, new effects are calculated by using `effects` lambda and then performed.
76109

110+
When `query` returns `nil`, feedback loops doesn't perform any effect.
111+
112+
- parameter query: Part of state that controls feedback loop.
113+
- parameter effects: Chooses which effects to perform for certain query result.
114+
- returns: Feedback loop performing the effects.
115+
*/
116+
public func react<State, Query: Equatable, Event>(
117+
query: @escaping (State) -> Query?,
118+
effects: @escaping (Query) -> Signal<Event>
119+
) -> (Driver<State>) -> Signal<Event> {
120+
return { state in
121+
let observableSchedulerContext = ObservableSchedulerContext<State>(
122+
source: state.asObservable(),
123+
scheduler: Signal<Event>.SharingStrategy.scheduler.async
124+
)
125+
return react(query: query, effects: { effects($0).asObservable() })(observableSchedulerContext)
126+
.asSignal(onErrorSignalWith: .empty())
127+
}
128+
}
129+
130+
/**
77131
* State: State type of the system.
78-
* Control: Subset of state used to control the feedback loop.
132+
* Query: Subset of state used to control the feedback loop.
79133

80-
When query result exists (not `nil`), feedback loop is active and it performs effects.
134+
When `query` returns a value, that value is being passed into `effects` lambda to decide which effects should be performed.
135+
In case new `query` is different from the previous one, new effects are calculated by using `effects` lambda and then performed.
81136

82-
When query result is `nil`, feedback loops doesn't perform any effect.
137+
When `query` returns `nil`, feedback loops doesn't perform any effect.
83138

84-
- parameter query: State type of the system
85-
- parameter effects: Control state which is subset of state.
139+
- parameter query: Part of state that controls feedback loop.
140+
- parameter effects: Chooses which effects to perform for certain query result.
86141
- returns: Feedback loop performing the effects.
87142
*/
88-
public func react<State, Control, Event>(
89-
query: @escaping (State) -> Control?,
90-
effects: @escaping (Control) -> Observable<Event>
91-
) -> (ObservableSchedulerContext<State>) -> Observable<Event> {
143+
public func react<State, Query, Event>(
144+
query: @escaping (State) -> Query?,
145+
effects: @escaping (Query) -> Observable<Event>
146+
) -> (ObservableSchedulerContext<State>) -> Observable<Event> {
92147
return { state in
93148
return state.map(query)
94149
.distinctUntilChanged { $0 != nil }
95-
.flatMapLatest { (control: Control?) -> Observable<Event> in
150+
.flatMapLatest { (control: Query?) -> Observable<Event> in
96151
guard let control = control else {
97152
return Observable<Event>.empty()
98153
}
@@ -104,23 +159,22 @@ public func react<State, Control, Event>(
104159
}
105160

106161
/**
107-
Control feedback loop that tries to immediatelly perform the latest required effect.
108-
109162
* State: State type of the system.
110-
* Control: Subset of state used to control the feedback loop.
163+
* Query: Subset of state used to control the feedback loop.
111164

112-
When query result exists (not `nil`), feedback loop is active and it performs effects.
165+
When `query` returns a value, that value is being passed into `effects` lambda to decide which effects should be performed.
166+
In case new `query` is different from the previous one, new effects are calculated by using `effects` lambda and then performed.
113167

114-
When query result is `nil`, feedback loops doesn't perform any effect.
168+
When `query` returns `nil`, feedback loops doesn't perform any effect.
115169

116-
- parameter query: State type of the system
117-
- parameter effects: Control state which is subset of state.
170+
- parameter query: Part of state that controls feedback loop.
171+
- parameter effects: Chooses which effects to perform for certain query result.
118172
- returns: Feedback loop performing the effects.
119173
*/
120-
public func react<State, Control, Event>(
121-
query: @escaping (State) -> Control?,
122-
effects: @escaping (Control) -> Signal<Event>
123-
) -> (Driver<State>) -> Signal<Event> {
174+
public func react<State, Query, Event>(
175+
query: @escaping (State) -> Query?,
176+
effects: @escaping (Query) -> Signal<Event>
177+
) -> (Driver<State>) -> Signal<Event> {
124178
return { state in
125179
let observableSchedulerContext = ObservableSchedulerContext<State>(
126180
source: state.asObservable(),
@@ -132,22 +186,22 @@ public func react<State, Control, Event>(
132186
}
133187

134188
/**
135-
Control feedback loop that tries to immediatelly perform the latest required effect.
136-
137189
* State: State type of the system.
138-
* Control: Subset of state used to control the feedback loop.
190+
* Query: Subset of state used to control the feedback loop.
139191

140-
When query result exists (not `nil`), feedback loop is active and it performs effects.
192+
When `query` returns some set of values, each value is being passed into `effects` lambda to decide which effects should be performed.
141193

142-
When query result is `nil`, feedback loops doesn't perform any effect.
194+
* Effects are not interrupted for elements in the new `query` that were present in the `old` query.
195+
* Effects are cancelled for elements present in `old` query but not in `new` query.
196+
* In case new elements are present in `new` query (and not in `old` query) they are being passed to the `effects` lambda and resulting effects are being performed.
143197

144-
- parameter query: State type of the system
145-
- parameter effects: Control state which is subset of state.
198+
- parameter query: Part of state that controls feedback loop.
199+
- parameter effects: Chooses which effects to perform for certain query element.
146200
- returns: Feedback loop performing the effects.
147201
*/
148-
public func react<State, Control, Event>(
149-
query: @escaping (State) -> Set<Control>,
150-
effects: @escaping (Control) -> Observable<Event>
202+
public func react<State, Query, Event>(
203+
query: @escaping (State) -> Set<Query>,
204+
effects: @escaping (Query) -> Observable<Event>
151205
) -> (ObservableSchedulerContext<State>) -> Observable<Event> {
152206
return { state in
153207
let query = state.map(query)
@@ -169,35 +223,35 @@ public func react<State, Control, Event>(
169223
extension ObservableType {
170224
// This is important to avoid reentrancy issues. Completed event is only used for cleanup
171225
fileprivate func takeUntilWithCompletedAsync<O>(_ other: Observable<O>, scheduler: ImmediateSchedulerType) -> Observable<E> {
172-
// this little piggy will delay completed event
173-
let completeAsSoonAsPossible = Observable<E>.empty().observeOn(scheduler)
174-
return other
175-
.take(1)
176-
.map { _ in completeAsSoonAsPossible }
177-
// this little piggy will ensure self is being run first
178-
.startWith(self.asObservable())
179-
// this little piggy will ensure that new events are being blocked immediatelly
180-
.switchLatest()
226+
// this little piggy will delay completed event
227+
let completeAsSoonAsPossible = Observable<E>.empty().observeOn(scheduler)
228+
return other
229+
.take(1)
230+
.map { _ in completeAsSoonAsPossible }
231+
// this little piggy will ensure self is being run first
232+
.startWith(self.asObservable())
233+
// this little piggy will ensure that new events are being blocked immediatelly
234+
.switchLatest()
181235
}
182236
}
183237

184238
/**
185-
Control feedback loop that tries to immediatelly perform the latest required effect.
186-
187239
* State: State type of the system.
188-
* Control: Subset of state used to control the feedback loop.
240+
* Query: Subset of state used to control the feedback loop.
189241

190-
When query result exists (not `nil`), feedback loop is active and it performs effects.
242+
When `query` returns some set of values, each value is being passed into `effects` lambda to decide which effects should be performed.
191243

192-
When query result is `nil`, feedback loops doesn't perform any effect.
244+
* Effects are not interrupted for elements in the new `query` that were present in the `old` query.
245+
* Effects are cancelled for elements present in `old` query but not in `new` query.
246+
* In case new elements are present in `new` query (and not in `old` query) they are being passed to the `effects` lambda and resulting effects are being performed.
193247

194-
- parameter query: State type of the system
195-
- parameter effects: Control state which is subset of state.
248+
- parameter query: Part of state that controls feedback loop.
249+
- parameter effects: Chooses which effects to perform for certain query element.
196250
- returns: Feedback loop performing the effects.
197251
*/
198-
public func react<State, Control, Event>(
199-
query: @escaping (State) -> Set<Control>,
200-
effects: @escaping (Control) -> Signal<Event>
252+
public func react<State, Query, Event>(
253+
query: @escaping (State) -> Set<Query>,
254+
effects: @escaping (Query) -> Signal<Event>
201255
) -> (Driver<State>) -> Signal<Event> {
202256
return { (state: Driver<State>) -> Signal<Event> in
203257
let observableSchedulerContext = ObservableSchedulerContext<State>(
@@ -225,15 +279,15 @@ extension Observable {
225279
Contains subscriptions and events.
226280
- `subscriptions` map a system state to UI presentation.
227281
- `events` map events from UI to events of a given system.
228-
*/
282+
*/
229283
public class Bindings<Event>: Disposable {
230284
fileprivate let subscriptions: [Disposable]
231285
fileprivate let events: [Observable<Event>]
232286

233287
/**
234288
- parameters:
235-
- subscriptions: mappings of a system state to UI presentation.
236-
- events: mappings of events from UI to events of a given system
289+
- subscriptions: mappings of a system state to UI presentation.
290+
- events: mappings of events from UI to events of a given system
237291
*/
238292
public init(subscriptions: [Disposable], events: [Observable<Event>]) {
239293
self.subscriptions = subscriptions
@@ -242,8 +296,8 @@ public class Bindings<Event>: Disposable {
242296

243297
/**
244298
- parameters:
245-
- subscriptions: mappings of a system state to UI presentation.
246-
- events: mappings of events from UI to events of a given system
299+
- subscriptions: mappings of a system state to UI presentation.
300+
- events: mappings of events from UI to events of a given system
247301
*/
248302
public init(subscriptions: [Disposable], events: [Signal<Event>]) {
249303
self.subscriptions = subscriptions
@@ -302,15 +356,16 @@ public func bind<State, Event>(_ bindings: @escaping (Driver<State>) -> (Binding
302356
*/
303357
public func bind<State, Event, WeakOwner>(_ owner: WeakOwner, _ bindings: @escaping (WeakOwner, Driver<State>) -> (Bindings<Event>))
304358
-> (Driver<State>) -> Signal<Event> where WeakOwner: AnyObject {
305-
return bind(bindingsStrongify(owner, bindings))
359+
return bind(bindingsStrongify(owner, bindings))
306360
}
307361

308362
private func bindingsStrongify<Event, O, WeakOwner>(_ owner: WeakOwner, _ bindings: @escaping (WeakOwner, O) -> (Bindings<Event>))
309363
-> (O) -> (Bindings<Event>) where WeakOwner: AnyObject {
310-
return { [weak owner] state -> Bindings<Event> in
311-
guard let strongOwner = owner else {
312-
return Bindings(subscriptions: [], events: [Observable<Event>]())
364+
return { [weak owner] state -> Bindings<Event> in
365+
guard let strongOwner = owner else {
366+
return Bindings(subscriptions: [], events: [Observable<Event>]())
367+
}
368+
return bindings(strongOwner, state)
313369
}
314-
return bindings(strongOwner, state)
315-
}
316370
}
371+

0 commit comments

Comments
 (0)