Skip to content

Commit e223738

Browse files
committed
Change Store to use Signal pipe instead of MutableProperty.
1 parent 644201a commit e223738

File tree

7 files changed

+97
-90
lines changed

7 files changed

+97
-90
lines changed

Sources/ComposableArchitecture/Internal/Deprecations.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
[ID], (Data.Index, EachAction), ForEach<[(offset: Int, element: ID)], ID, EachContent>
7777
>
7878
{
79-
let data = store.$state.value
79+
let data = store.state
8080
self.data = data
8181
self.content = {
8282
WithViewStore(store.scope(state: { $0.map { $0[keyPath: id] } })) { viewStore in

Sources/ComposableArchitecture/Store.swift

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +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-
@MutableProperty
118-
private(set) var state: State
117+
private(set) var state: State {
118+
didSet {
119+
statePipe.input.send(value: state)
120+
}
121+
}
122+
private let statePipe = Signal<State, Never>.pipe()
123+
internal var producer: Effect<State, Never> {
124+
Property<State>(initial: self.state, then: self.statePipe.output.producer).producer
125+
}
119126

120127
private var isSending = false
121128
private let reducer: (inout State, Action) -> Effect<Action, Never>
@@ -294,11 +301,11 @@ public final class Store<State, Action> {
294301
environment: ()
295302
)
296303

297-
localStore.parentDisposable = self.$state.producer
304+
localStore.parentDisposable = self.producer
298305
.skip(first: 1)
299306
.startWithValues { [weak localStore] newValue in
300307
guard !isSending else { return }
301-
localStore?.$state.value = toLocalState(newValue)
308+
localStore?.state = toLocalState(newValue)
302309
}
303310

304311
return localStore
@@ -333,7 +340,7 @@ public final class Store<State, Action> {
333340
return localState
334341
}
335342

336-
return toLocalState(self.$state.producer)
343+
return toLocalState(self.producer)
337344
.map { localState in
338345
let localStore = Store<LocalState, LocalAction>(
339346
initialState: localState,
@@ -344,7 +351,7 @@ public final class Store<State, Action> {
344351
},
345352
environment: ()
346353
)
347-
localStore.parentDisposable = self.$state.producer.startWithValues {
354+
localStore.parentDisposable = self.producer.startWithValues {
348355
[weak localStore] state in
349356
guard let localStore = localStore else { return }
350357
localStore.state = extractLocalState(state) ?? localStore.state
@@ -370,10 +377,10 @@ public final class Store<State, Action> {
370377
guard !self.isSending else { return }
371378

372379
self.isSending = true
373-
var currentState = self.$state.value
380+
var currentState = self.state
374381
defer {
375382
self.isSending = false
376-
self.$state.value = currentState
383+
self.state = currentState
377384
}
378385

379386
while !self.bufferedActions.isEmpty {

Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
// views for elements no longer in the collection.
101101
//
102102
// Feedback filed: https://gist.github.com/stephencelis/cdf85ae8dab437adc998fb0204ed9a6b
103-
let element = store.$state.value[id: id]!
103+
let element = store.state[id: id]!
104104
return content(
105105
store.scope(
106106
state: { $0[id: id] ?? element },

Sources/ComposableArchitecture/SwiftUI/SwitchStore.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1160,7 +1160,7 @@
11601160
let message = """
11611161
Warning: SwitchStore.body@\(self.file):\(self.line)
11621162
1163-
"\(debugCaseOutput(self.store.wrappedValue.$state.value))" was encountered by a \
1163+
"\(debugCaseOutput(self.store.wrappedValue.state))" was encountered by a \
11641164
"SwitchStore" that does not handle this case.
11651165
11661166
Make sure that you exhaustively provide a "CaseLet" view for each case in "\(State.self)", \

Sources/ComposableArchitecture/TestSupport/TestStore.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@
528528
environment: self.environment,
529529
file: self.file,
530530
fromLocalAction: { self.fromLocalAction(fromLocalAction($0)) },
531-
initialState: self.store.$state.value,
531+
initialState: self.store.state,
532532
line: self.line,
533533
reducer: self.reducer,
534534
toLocalState: { toLocalState(self.toLocalState($0)) }

Sources/ComposableArchitecture/ViewStore.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public final class ViewStore<State, Action> {
6464

6565
private let _send: (Action) -> Void
6666
private var _state: State
67-
fileprivate let _stateSubject = Signal<State, Never>.pipe()
67+
fileprivate let statePipe = Signal<State, Never>.pipe()
6868
private var viewDisposable: Disposable?
6969

7070
/// Initializes a view store from a store.
@@ -77,10 +77,10 @@ public final class ViewStore<State, Action> {
7777
_ store: Store<State, Action>,
7878
removeDuplicates isDuplicate: @escaping (State, State) -> Bool
7979
) {
80-
self._state = store.$state.value
80+
self._state = store.state
8181
self._send = store.send
8282

83-
self.viewDisposable = store.$state.producer
83+
self.viewDisposable = store.producer
8484
.skipRepeats(isDuplicate)
8585
.startWithValues { [weak self] in
8686
guard let self = self else { return }
@@ -90,7 +90,7 @@ public final class ViewStore<State, Action> {
9090
}
9191
#endif
9292
self._state = $0
93-
self._stateSubject.input.send(value: $0)
93+
self.statePipe.input.send(value: $0)
9494
}
9595
}
9696

@@ -297,7 +297,7 @@ public struct StoreProducer<State>: SignalProducerConvertible {
297297

298298
fileprivate init<Action>(viewStore: ViewStore<State, Action>) {
299299
self.viewStore = viewStore
300-
self.upstream = Property<State>(initial: viewStore.state, then: viewStore._stateSubject.output).producer
300+
self.upstream = Property<State>(initial: viewStore.state, then: viewStore.statePipe.output).producer
301301
// .on(completed: { [viewStore = self.viewStore] in
302302
// _ = viewStore
303303
// })

Tests/ComposableArchitectureTests/StoreTests.swift

Lines changed: 73 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ final class StoreTests: XCTestCase {
8080
let childStore = parentStore.scope(state: String.init)
8181

8282
var values: [String] = []
83-
childStore.$state.producer
83+
childStore.producer
8484
.startWithValues { values.append($0) }
8585

8686
XCTAssertEqual(values, ["0"])
@@ -101,7 +101,7 @@ final class StoreTests: XCTestCase {
101101
let childViewStore = ViewStore(childStore)
102102

103103
var values: [Int] = []
104-
parentStore.$state.producer
104+
parentStore.producer
105105
.startWithValues { values.append($0) }
106106

107107
XCTAssertEqual(values, [0])
@@ -123,7 +123,7 @@ final class StoreTests: XCTestCase {
123123
parentStore
124124
.producerScope(state: { $0.map { "\($0)" }.skipRepeats() })
125125
.startWithValues { childStore in
126-
childStore.$state.producer
126+
childStore.producer
127127
.startWithValues { outputs.append($0) }
128128
}
129129

@@ -282,7 +282,7 @@ final class StoreTests: XCTestCase {
282282

283283
parentStore
284284
.producerScope { $0.skipRepeats() }
285-
.startWithValues { outputs.append($0.$state.value) }
285+
.startWithValues { outputs.append($0.state) }
286286

287287
XCTAssertEqual(outputs, [0])
288288

@@ -451,73 +451,73 @@ final class StoreTests: XCTestCase {
451451
}
452452

453453
// This test commented out as it falls foul of ReactiveSwift's
454-
// `MutableProperty` not allowing nested modifications 😢
455-
456-
// func testBufferedActionProcessing() {
457-
// struct ChildState: Equatable {
458-
// var count: Int?
459-
// }
460-
//
461-
// let childReducer = Reducer<ChildState, Int?, Void> { state, action, _ in
462-
// state.count = action
463-
// return .none
464-
// }
465-
//
466-
// struct ParentState: Equatable {
467-
// var count: Int?
468-
// var child: ChildState?
469-
// }
470-
//
471-
// enum ParentAction: Equatable {
472-
// case button
473-
// case child(Int?)
474-
// }
475-
//
476-
// var handledActions: [ParentAction] = []
477-
// let parentReducer = Reducer.combine([
478-
// childReducer
479-
// .optional()
480-
// .pullback(
481-
// state: \.child,
482-
// action: /ParentAction.child,
483-
// environment: {}
484-
// ),
485-
// Reducer<ParentState, ParentAction, Void> { state, action, _ in
486-
// handledActions.append(action)
487-
//
488-
// switch action {
489-
// case .button:
490-
// state.child = .init(count: nil)
491-
// return .none
492-
//
493-
// case .child(let childCount):
494-
// state.count = childCount
495-
// return .none
496-
// }
497-
// },
498-
// ])
499-
//
500-
// let parentStore = Store(
501-
// initialState: .init(),
502-
// reducer: parentReducer,
503-
// environment: ()
504-
// )
505-
//
506-
// parentStore
507-
// .scope(
508-
// state: \.child,
509-
// action: ParentAction.child
510-
// )
511-
// .ifLet { childStore in
512-
// ViewStore(childStore).send(2)
513-
// }
514-
//
515-
// XCTAssertEqual(handledActions, [])
516-
//
517-
// parentStore.send(.button)
518-
// XCTAssertEqual(handledActions, [
519-
// .button,
520-
// .child(2)
521-
// ])
522-
// }
454+
// `Signal` not allowing nested sends 😢
455+
456+
func disabled_testBufferedActionProcessing() {
457+
struct ChildState: Equatable {
458+
var count: Int?
459+
}
460+
461+
let childReducer = Reducer<ChildState, Int?, Void> { state, action, _ in
462+
state.count = action
463+
return .none
464+
}
465+
466+
struct ParentState: Equatable {
467+
var count: Int?
468+
var child: ChildState?
469+
}
470+
471+
enum ParentAction: Equatable {
472+
case button
473+
case child(Int?)
474+
}
475+
476+
var handledActions: [ParentAction] = []
477+
let parentReducer = Reducer.combine([
478+
childReducer
479+
.optional()
480+
.pullback(
481+
state: \.child,
482+
action: /ParentAction.child,
483+
environment: {}
484+
),
485+
Reducer<ParentState, ParentAction, Void> { state, action, _ in
486+
handledActions.append(action)
487+
488+
switch action {
489+
case .button:
490+
state.child = .init(count: nil)
491+
return .none
492+
493+
case .child(let childCount):
494+
state.count = childCount
495+
return .none
496+
}
497+
},
498+
])
499+
500+
let parentStore = Store(
501+
initialState: .init(),
502+
reducer: parentReducer,
503+
environment: ()
504+
)
505+
506+
parentStore
507+
.scope(
508+
state: \.child,
509+
action: ParentAction.child
510+
)
511+
.ifLet { childStore in
512+
ViewStore(childStore).send(2)
513+
}
514+
515+
XCTAssertEqual(handledActions, [])
516+
517+
parentStore.send(.button)
518+
XCTAssertEqual(handledActions, [
519+
.button,
520+
.child(2)
521+
])
522+
}
523523
}

0 commit comments

Comments
 (0)