Skip to content

Commit f2f431c

Browse files
committed
Revert Store to use MutableProperty, revert to previous
implementation of `send` to avoid problem with nested sending in ReactiveSwift Signal. Disabled `testPublisherOwnsViewStore` test.
1 parent e223738 commit f2f431c

File tree

4 files changed

+51
-46
lines changed

4 files changed

+51
-46
lines changed

Sources/ComposableArchitecture/Store.swift

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,15 @@ import ReactiveSwift
114114
/// See also: ``ViewStore`` to understand how one observes changes to the state in a ``Store`` and
115115
/// sends user actions.
116116
public final class Store<State, Action> {
117-
private(set) var state: State {
118-
didSet {
119-
statePipe.input.send(value: state)
120-
}
121-
}
122-
private let statePipe = Signal<State, Never>.pipe()
117+
@MutableProperty
118+
private(set) var state: State
123119
internal var producer: Effect<State, Never> {
124-
Property<State>(initial: self.state, then: self.statePipe.output.producer).producer
120+
self.$state.producer
125121
}
126122

127123
private var isSending = false
128124
private let reducer: (inout State, Action) -> Effect<Action, Never>
125+
private var synchronousActionsToSend: [Action] = []
129126
private var bufferedActions: [Action] = []
130127
internal var effectDisposables: [UUID: Disposable] = [:]
131128
internal var parentDisposable: Disposable?
@@ -373,41 +370,50 @@ public final class Store<State, Action> {
373370
}
374371

375372
func send(_ action: Action) {
376-
self.bufferedActions.append(action)
377-
guard !self.isSending else { return }
378-
379-
self.isSending = true
380-
var currentState = self.state
381-
defer {
382-
self.isSending = false
383-
self.state = currentState
373+
if !self.isSending {
374+
self.synchronousActionsToSend.append(action)
375+
} else {
376+
self.bufferedActions.append(action)
377+
return
384378
}
385379

386-
while !self.bufferedActions.isEmpty {
387-
let action = self.bufferedActions.removeFirst()
388-
let effect = self.reducer(&currentState, action)
380+
while !self.synchronousActionsToSend.isEmpty || !self.bufferedActions.isEmpty {
381+
let action =
382+
!self.synchronousActionsToSend.isEmpty
383+
? self.synchronousActionsToSend.removeFirst()
384+
: self.bufferedActions.removeFirst()
385+
386+
self.isSending = true
387+
let effect = self.reducer(&self.state, action)
388+
self.isSending = false
389389

390390
var didComplete = false
391-
let uuid = UUID()
391+
let effectID = UUID()
392392

393+
var isProcessingEffects = true
393394
let observer = Signal<Action, Never>.Observer(
394395
value: { [weak self] action in
395-
self?.send(action)
396+
if isProcessingEffects {
397+
self?.synchronousActionsToSend.append(action)
398+
} else {
399+
self?.send(action)
400+
}
396401
},
402+
failed: .none,
397403
completed: { [weak self] in
398404
didComplete = true
399-
self?.effectDisposables.removeValue(forKey: uuid)?.dispose()
405+
self?.effectDisposables.removeValue(forKey: effectID)?.dispose()
400406
},
401407
interrupted: { [weak self] in
402408
didComplete = true
403-
self?.effectDisposables.removeValue(forKey: uuid)?.dispose()
409+
self?.effectDisposables.removeValue(forKey: effectID)?.dispose()
404410
}
405411
)
406-
407412
let effectDisposable = effect.start(observer)
413+
isProcessingEffects = false
408414

409415
if !didComplete {
410-
self.effectDisposables[uuid] = effectDisposable
416+
self.effectDisposables[effectID] = effectDisposable
411417
} else {
412418
effectDisposable.dispose()
413419
}

Sources/ComposableArchitecture/ViewStore.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,6 @@ public struct StoreProducer<State>: SignalProducerConvertible {
298298
fileprivate init<Action>(viewStore: ViewStore<State, Action>) {
299299
self.viewStore = viewStore
300300
self.upstream = Property<State>(initial: viewStore.state, then: viewStore.statePipe.output).producer
301-
// .on(completed: { [viewStore = self.viewStore] in
302-
// _ = viewStore
303-
// })
304301
}
305302

306303
private init(

Tests/ComposableArchitectureTests/StoreTests.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -447,12 +447,11 @@ final class StoreTests: XCTestCase {
447447

448448
viewStore.send(0)
449449

450-
XCTAssertEqual(emissions, [0, 3])
450+
XCTAssertEqual(emissions, [0, 1, 2, 3])
451451
}
452452

453-
// This test commented out as it falls foul of ReactiveSwift's
454-
// `Signal` not allowing nested sends 😢
455-
453+
// This test commented out as it falls foul of ReactiveSwift's
454+
// `Signal` not allowing nested sends 😢
456455
func disabled_testBufferedActionProcessing() {
457456
struct ChildState: Equatable {
458457
var count: Int?

Tests/ComposableArchitectureTests/ViewStoreTests.swift

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -125,21 +125,24 @@ final class ViewStoreTests: XCTestCase {
125125
}
126126
#endif
127127

128-
// func testPublisherOwnsViewStore() {
129-
// let reducer = Reducer<Int, Void, Void> { count, _, _ in
130-
// count += 1
131-
// return .none
132-
// }
133-
// let store = Store(initialState: 0, reducer: reducer, environment: ())
134-
//
135-
// var results: [Int] = []
136-
// ViewStore(store)
137-
// .produced.producer.logEvents(identifier: "test")
138-
// .startWithValues { results.append($0) }
139-
//
140-
// ViewStore(store).send(())
141-
// XCTAssertEqual(results, [0, 1])
142-
// }
128+
// disabled as the fix for this would be onerous with
129+
// ReactiveSwift, forcing explicit disposable of any use of
130+
// `ViewStore.produced.producer`
131+
func disabled_testPublisherOwnsViewStore() {
132+
let reducer = Reducer<Int, Void, Void> { count, _, _ in
133+
count += 1
134+
return .none
135+
}
136+
let store = Store(initialState: 0, reducer: reducer, environment: ())
137+
138+
var results: [Int] = []
139+
ViewStore(store)
140+
.produced.producer
141+
.startWithValues { results.append($0) }
142+
143+
ViewStore(store).send(())
144+
XCTAssertEqual(results, [0, 1])
145+
}
143146
}
144147

145148
private struct State: Equatable {

0 commit comments

Comments
 (0)