Skip to content

Commit f6e8d3a

Browse files
committed
Add new functionality to Middleware
1 parent ddb3a63 commit f6e8d3a

File tree

4 files changed

+61
-28
lines changed

4 files changed

+61
-28
lines changed

Docs/Getting Started Guide.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,9 @@ Let's take a look at a quick example that shows how ReactiveReSwift supports Red
201201
The simplest example of a middleware, is one that prints all actions to the console. Here's how you can implement it:
202202

203203
```swift
204-
let loggingMiddleware = Middleware<State> { getState, dispatch, action in
204+
let loggingMiddleware = Middleware<State>().sideEffect { getState, dispatch, action in
205205
// perform middleware logic
206206
print(action)
207-
// call next middleware
208-
return action
209207
}
210208
```
211209
You can define which middleware you would like to use when creating your store:
@@ -219,3 +217,12 @@ Store(
219217
)
220218
```
221219
The actions will pass through the middleware in the order in which they are arranged in the `Middleware` initializer, however ideally middleware should not make any assumptions about when exactly it is called.
220+
221+
`Middleware` supports multiple different operations.
222+
223+
In no particular order, some of the more important operations are:
224+
225+
- `sideEffect(_:)` Gives access to the `dispatch(_:)` function. Does not return, to ensure that only side effects reside inside.
226+
- `filter(_:)` If the predicate function passed to filter passes, keep the action, otherwise discard the action.
227+
- `map(_:)` Apply a transformative function to the action.
228+
- `flatMap(_:)` Essentially a `map` that can also filter out the action by returning `nil`

ReactiveReSwift/CoreTypes/Middleware.swift

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@ public struct Middleware<State: StateType> {
1616

1717
private let transform: (GetState, DispatchFunction, Action) -> Action?
1818

19+
/// Create a blank slate Middleware.
20+
public init() {
21+
self.transform = { $2 }
22+
}
23+
1924
/**
2025
Initialises the middleware with a transformative function.
2126

2227
- parameter transform: The function that will be able to modify passed actions.
2328
*/
24-
public init(_ transform: @escaping (GetState, DispatchFunction, Action) -> Action?) {
29+
internal init(_ transform: @escaping (GetState, DispatchFunction, Action) -> Action?) {
2530
self.transform = transform
2631
}
2732

@@ -40,33 +45,41 @@ public struct Middleware<State: StateType> {
4045
return transform(state, dispatch, argument)
4146
}
4247

48+
/// Safe encapsulation of side effects guaranteed not to affect the action being passed through the middleware.
49+
public func sideEffect(_ effect: @escaping (GetState, DispatchFunction, Action) -> Void) -> Middleware<State> {
50+
return Middleware<State> {
51+
guard let action = self.transform($0, $1, $2) else { return nil }
52+
effect($0, $1, action)
53+
return action
54+
}
55+
}
56+
4357
/// Concatenates the transform function of the passed `Middleware` onto the callee's transform.
4458
public func concat(_ other: Middleware<State>) -> Middleware<State> {
45-
return flatMap(other.transform)
59+
return Middleware<State> {
60+
guard let action = self.transform($0, $1, $2) else { return nil }
61+
return other.transform($0, $1, action)
62+
}
4663
}
4764

4865
/// Concatenates the transform function onto the callee's transform.
49-
public func map(_ transform: @escaping (GetState, DispatchFunction, Action) -> Action) -> Middleware<State> {
66+
public func map(_ transform: @escaping (GetState, Action) -> Action) -> Middleware<State> {
5067
return flatMap(transform)
5168
}
5269

5370
/// Concatenates the transform function onto the callee's transform.
54-
public func flatMap(_ transform: @escaping (GetState, DispatchFunction, Action) -> Action?) -> Middleware<State> {
71+
public func flatMap(_ transform: @escaping (GetState, Action) -> Action?) -> Middleware<State> {
5572
return Middleware<State> {
56-
if let action = self.transform($0, $1, $2) {
57-
return transform($0, $1, action)
58-
}
59-
return nil
73+
guard let action = self.transform($0, $1, $2) else { return nil }
74+
return transform($0, action)
6075
}
6176
}
6277

6378
/// Drop the Action if `predicate(action) != true`.
64-
public func filter(_ predicate: @escaping (Action) -> Bool) -> Middleware<State> {
79+
public func filter(_ predicate: @escaping (GetState, Action) -> Bool) -> Middleware<State> {
6580
return Middleware<State> {
66-
if let action = self.transform($0, $1, $2), predicate(action) {
67-
return action
68-
}
69-
return nil
81+
guard let action = self.transform($0, $1, $2), predicate($0, action) else { return nil }
82+
return action
7083
}
7184
}
7285
}

ReactiveReSwiftTests/MiddlewareFakes.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,36 @@
88

99
import ReactiveReSwift
1010

11-
let firstMiddleware = Middleware<TestStringAppState> { state, dispatch, action in
11+
let firstMiddleware = Middleware<TestStringAppState>().map { state, action in
1212
if let action = action as? SetValueStringAction {
1313
return SetValueStringAction(action.value + " First Middleware")
1414
}
1515
return action
1616
}
1717

18-
let secondMiddleware = Middleware<TestStringAppState> { state, dispatch, action in
18+
let secondMiddleware = Middleware<TestStringAppState>().map { state, action in
1919
if let action = action as? SetValueStringAction {
2020
return SetValueStringAction(action.value + " Second Middleware")
2121
}
2222
return action
2323
}
2424

25-
let dispatchingMiddleware = Middleware<TestStringAppState> { state, dispatch, action in
25+
let dispatchingMiddleware = Middleware<TestStringAppState>().sideEffect { state, dispatch, action in
2626
if let action = action as? SetValueAction {
2727
dispatch(SetValueStringAction("\(action.value)"))
28-
return nil
2928
}
30-
return action
29+
}.filter { _, action in
30+
!(action is SetValueAction)
3131
}
3232

33-
let stateAccessingMiddleware = Middleware<TestStringAppState> { state, dispatch, action in
33+
let stateAccessingMiddleware = Middleware<TestStringAppState>().sideEffect { state, dispatch, action in
3434
if let action = action as? SetValueStringAction {
3535
//Avoid endless recursion by checking if we've exactly this action
3636
if state().testValue == "OK" && action.value != "Not OK" {
3737
dispatch(SetValueStringAction("Not OK"))
38-
//Swallow the action
39-
return nil
4038
}
4139
}
42-
return action
40+
}.filter { state, action in
41+
print("Swallowing action \(action)")
42+
return (action as? SetValueStringAction)?.value == "Not OK"
4343
}

ReactiveReSwiftTests/Observable/StoreMiddlewareTests.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,27 @@ class StoreMiddlewareTests: XCTestCase {
6767
it middleware should not be executed if the previous middleware returned nil
6868
*/
6969
func testMiddlewareSkipsReducersWhenPassedNil() {
70-
let filteringMiddleware = Middleware<TestStringAppState> { _, _, _ in nil }.map { _, _, action in XCTFail(); return action }
70+
let filteringMiddleware1 = Middleware<TestStringAppState>().filter({ _, _ in false }).sideEffect { _, _, _ in XCTFail() }
71+
let filteringMiddleware2 = Middleware<TestStringAppState>().filter({ _, _ in false }).flatMap { _, _ in XCTFail(); return nil }
7172

7273
let property = ObservableProperty(TestStringAppState(testValue: "OK"))
73-
let store = Store(reducer: testValueStringReducer,
74+
75+
var store = Store(reducer: testValueStringReducer,
7476
stateType: TestStringAppState.self,
7577
observable: property,
76-
middleware: filteringMiddleware)
78+
middleware: Middleware(filteringMiddleware1, filteringMiddleware2))
79+
store.dispatch(SetValueStringAction("Action That Won't Go Through"))
80+
81+
store = Store(reducer: testValueStringReducer,
82+
stateType: TestStringAppState.self,
83+
observable: property,
84+
middleware: filteringMiddleware1)
85+
store.dispatch(SetValueStringAction("Action That Won't Go Through"))
7786

87+
store = Store(reducer: testValueStringReducer,
88+
stateType: TestStringAppState.self,
89+
observable: property,
90+
middleware: filteringMiddleware2)
7891
store.dispatch(SetValueStringAction("Action That Won't Go Through"))
7992
}
8093
}

0 commit comments

Comments
 (0)