Skip to content
This repository was archived by the owner on May 13, 2020. It is now read-only.

Commit ca4002c

Browse files
committed
Replace Middleware concept with a State Observable
1 parent eb9b0df commit ca4002c

File tree

3 files changed

+57
-59
lines changed

3 files changed

+57
-59
lines changed

RxReduce.xcodeproj/project.pbxproj

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
1A2A758C208D51E2009883BF /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2A7589208D51E2009883BF /* Store.swift */; };
1818
1A708BA320CF187300F30C9C /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A2A7583208D50BC009883BF /* RxCocoa.framework */; };
1919
1A708BA420CF187300F30C9C /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A2A7582208D50BC009883BF /* RxSwift.framework */; };
20-
1A708BA620CF1BFC00F30C9C /* RxCocoa.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1A2A7583208D50BC009883BF /* RxCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
21-
1A708BA720CF1BFC00F30C9C /* RxSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1A2A7582208D50BC009883BF /* RxSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
20+
1A708BA620CF1BFC00F30C9C /* RxCocoa.framework in Copy Items */ = {isa = PBXBuildFile; fileRef = 1A2A7583208D50BC009883BF /* RxCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
21+
1A708BA720CF1BFC00F30C9C /* RxSwift.framework in Copy Items */ = {isa = PBXBuildFile; fileRef = 1A2A7582208D50BC009883BF /* RxSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
2222
1A708BA920CF1F4F00F30C9C /* StoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A708BA820CF1F4F00F30C9C /* StoreTests.swift */; };
2323
1A708BAB20CF1F8000F30C9C /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A708BAA20CF1F8000F30C9C /* Actions.swift */; };
2424
1A708BAD20CF1FE100F30C9C /* States.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A708BAC20CF1FE100F30C9C /* States.swift */; };
2525
1A708BB020CF233600F30C9C /* Reducers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A708BAF20CF233600F30C9C /* Reducers.swift */; };
2626
1A7139792134E4A200903866 /* RxBlocking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A7139782134E4A200903866 /* RxBlocking.framework */; };
27-
1A71397A2134E4C400903866 /* RxBlocking.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1A7139782134E4A200903866 /* RxBlocking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
27+
1A71397A2134E4C400903866 /* RxBlocking.framework in Copy Items */ = {isa = PBXBuildFile; fileRef = 1A7139782134E4A200903866 /* RxBlocking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
2828
/* End PBXBuildFile section */
2929

3030
/* Begin PBXContainerItemProxy section */
@@ -38,16 +38,17 @@
3838
/* End PBXContainerItemProxy section */
3939

4040
/* Begin PBXCopyFilesBuildPhase section */
41-
1A708BA520CF1BE600F30C9C /* CopyFiles */ = {
41+
1A708BA520CF1BE600F30C9C /* Copy Items */ = {
4242
isa = PBXCopyFilesBuildPhase;
4343
buildActionMask = 2147483647;
4444
dstPath = "";
4545
dstSubfolderSpec = 10;
4646
files = (
47-
1A71397A2134E4C400903866 /* RxBlocking.framework in CopyFiles */,
48-
1A708BA620CF1BFC00F30C9C /* RxCocoa.framework in CopyFiles */,
49-
1A708BA720CF1BFC00F30C9C /* RxSwift.framework in CopyFiles */,
47+
1A71397A2134E4C400903866 /* RxBlocking.framework in Copy Items */,
48+
1A708BA620CF1BFC00F30C9C /* RxCocoa.framework in Copy Items */,
49+
1A708BA720CF1BFC00F30C9C /* RxSwift.framework in Copy Items */,
5050
);
51+
name = "Copy Items";
5152
runOnlyForDeploymentPostprocessing = 0;
5253
};
5354
/* End PBXCopyFilesBuildPhase section */
@@ -198,7 +199,7 @@
198199
1A2A756C208D4750009883BF /* Sources */,
199200
1A2A756D208D4750009883BF /* Frameworks */,
200201
1A2A756E208D4750009883BF /* Resources */,
201-
1A708BA520CF1BE600F30C9C /* CopyFiles */,
202+
1A708BA520CF1BE600F30C9C /* Copy Items */,
202203
);
203204
buildRules = (
204205
);

RxReduce/Store.swift

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,37 @@ import RxCocoa
1414
/// Internally, Reducers come from registered Mutator defines a Reducer.
1515
public final class Store<State: Equatable> {
1616

17-
/// A Middleware acts like a valve in which a dispatched Action passes through.
18-
/// It cannot mutate the State. Middlewares can be used for logging purposes for instance
19-
public typealias Middleware = (_ state: State, _ action: Action) -> Void
17+
/// Reducer correspond to the apply func of a Mutator
2018
private typealias Reducer = (State, Action) -> State
2119

22-
private var state: State
20+
private let stateSubject: BehaviorRelay<State>
2321
private var neededReducersPerSubState = [String: Int]()
2422
private var reducers = ContiguousArray<Reducer>()
25-
private var middlewares = ContiguousArray<Middleware>()
23+
private let serialDispatchScheduler: SerialDispatchQueueScheduler = {
24+
let serialQueue = DispatchQueue(label: "com.rxswiftcommunity.rxreduce.serialqueue")
25+
return SerialDispatchQueueScheduler.init(queue: serialQueue, internalSerialQueueName: "com.rxswiftcommunity.rxreduce.serialscheduler")
26+
}()
27+
28+
/// The global State is exposed via an Observable, just like some kind of "middleware".
29+
/// This global State will trigger a new value after a dispatch(action) has triggered a "onNext" event.
30+
public lazy var state: Observable<State> = {
31+
return self.stateSubject.asObservable()
32+
}()
2633

2734
/// Inits a Store with an initial State
2835
///
2936
/// - Parameter state: the initial State
3037
public init(withState state: State) {
31-
self.state = state
38+
39+
// Sets the initial State value
40+
self.stateSubject = BehaviorRelay<State>(value: state)
3241

3342
// We analyze the State children (aka SubState)
3443
// to be able to check that each of them will be handled
3544
// by a reducer function (it allows to be sure that if we
3645
// add a new SubState to the State, there is a reducer in charge
3746
// of its mutation)
38-
let stateMirror = Mirror(reflecting: self.state)
47+
let stateMirror = Mirror(reflecting: state)
3948
stateMirror
4049
.children
4150
.map { Mirror(reflecting: $0.value) }
@@ -75,14 +84,6 @@ public final class Store<State: Equatable> {
7584
self.neededReducersPerSubState[subStateType] = neededReducerForSubState - 1
7685
}
7786

78-
/// Registers a Middleware. Middlewares will be applied in sequence
79-
/// in the same order than their addition
80-
///
81-
/// - Parameter middleware: The Middleware to add
82-
public func register (middleware: @escaping Middleware) {
83-
self.middlewares.append(middleware)
84-
}
85-
8687
/// Dispatches an Action to the registered Reducers.
8788
/// The Action will first go through the Middlewares and
8889
/// then through the Reducers producing a mutated State.
@@ -100,18 +101,16 @@ public final class Store<State: Equatable> {
100101
// every received action is converted to an async action
101102
return action
102103
.toAsync()
103-
.do(onNext: { [unowned self] (action) in
104-
self.middlewares.forEach({ [unowned self] (middleware) in
105-
middleware(self.state, action)
106-
})
107-
})
108104
.map { [unowned self] (action) -> State in
109-
return self.reducers.reduce(self.state, { (currentState, reducer) -> State in
105+
return self.reducers.reduce(self.stateSubject.value, { (currentState, reducer) -> State in
110106
return reducer(currentState, action)
111107
})
112-
}.do(onNext: { [unowned self] (newState) in
113-
self.state = newState
114-
}).distinctUntilChanged()
108+
}
109+
.do(onNext: { [unowned self] (newState) in
110+
self.stateSubject.accept(newState)
111+
})
112+
.distinctUntilChanged()
113+
.subscribeOn(self.serialDispatchScheduler)
115114
}
116115

117116
/// Dispatches an Action to the registered Mutators but instead of

RxReduceTests/StoreTests.swift

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,23 @@ import RxCocoa
1212
import RxReduce
1313
import RxBlocking
1414

15-
class StoreTests: XCTestCase {
15+
final class StoreTests: XCTestCase {
1616

17-
let disposeBag = DisposeBag()
17+
private let disposeBag = DisposeBag()
1818

19-
let counterLens = Lens<TestState, CounterState> (get: { $0.counterState }) { (testState, counterState) -> TestState in
19+
private let counterLens = Lens<TestState, CounterState> (get: { $0.counterState }) { (testState, counterState) -> TestState in
2020
var mutableTestState = testState
2121
mutableTestState.counterState = counterState
2222
return mutableTestState
2323
}
2424

25-
let userLens = Lens<TestState, UserState> (get: { $0.userState }) { (testState, userState) -> TestState in
25+
private let userLens = Lens<TestState, UserState> (get: { $0.userState }) { (testState, userState) -> TestState in
2626
var mutableTestState = testState
2727
mutableTestState.userState = userState
2828
return mutableTestState
2929
}
3030

31-
lazy var store: Store<TestState> = {
31+
private lazy var store: Store<TestState> = {
3232
let store = Store<TestState>(withState: TestState(counterState: .empty, userState: .loggedOut))
3333
let counterMutator = Mutator<TestState, CounterState>(lens: counterLens, reducer: counterReduce)
3434
let userMutator = Mutator<TestState, UserState>(lens: userLens, reducer: userReduce)
@@ -40,7 +40,11 @@ class StoreTests: XCTestCase {
4040
}()
4141

4242
override func tearDown() {
43-
store.dispatch(action: ClearAction()).subscribe().disposed(by: self.disposeBag)
43+
do {
44+
_ = try store.dispatch(action: ClearAction()).toBlocking(timeout: 1).single()
45+
} catch {
46+
XCTFail(error.localizedDescription)
47+
}
4448
}
4549

4650
func testSynchronousAction() throws {
@@ -114,29 +118,23 @@ class StoreTests: XCTestCase {
114118
}
115119
}
116120

117-
func testMiddlewares () {
118-
119-
let exp = expectation(description: "Middleware subscription")
121+
func testStateObservable () throws {
122+
let exp = expectation(description: "State Observable Expectation")
120123

121124
let increaseAction = IncreaseAction(increment: 10)
122125

123-
let middlewareStore = Store<TestState>(withState: TestState(counterState: .empty, userState: .loggedOut))
124-
125-
let counterMutator = Mutator<TestState, CounterState>(lens: counterLens, reducer: counterReduce)
126-
let userMutator = Mutator<TestState, UserState>(lens: userLens, reducer: userReduce)
127-
128-
middlewareStore.register(mutator: counterMutator)
129-
middlewareStore.register(mutator: userMutator)
130-
131-
middlewareStore.register { (state, action) in
132-
exp.fulfill()
133-
}
134-
135-
let subscription = middlewareStore.dispatch(action: increaseAction).subscribe()
136-
137-
waitForExpectations(timeout: 1) { (_) in
138-
subscription.dispose()
139-
}
140-
126+
Observable.combineLatest(self.store.dispatch(action: increaseAction), self.store.state) { (newState1, newState2) -> (TestState, TestState) in
127+
return (newState1, newState2)
128+
}.subscribe(onNext: { (states) in
129+
guard states.0 == TestState(counterState: CounterState.increasing(10), userState: UserState.loggedOut) else {
130+
return
131+
}
132+
133+
if states.0 == states.1 {
134+
exp.fulfill()
135+
}
136+
}).disposed(by: self.disposeBag)
137+
138+
waitForExpectations(timeout: 1)
141139
}
142140
}

0 commit comments

Comments
 (0)