Skip to content

Commit 0d4d99b

Browse files
committed
Add SideEffect type and update ActionLens
1 parent d3e310f commit 0d4d99b

File tree

8 files changed

+132
-32
lines changed

8 files changed

+132
-32
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
extension Bool {
2+
func `if`<T>(true truth: @autoclosure () throws -> T, false falsity: @autoclosure () throws -> T) rethrows -> T {
3+
if self {
4+
return try truth()
5+
} else {
6+
return try falsity()
7+
}
8+
}
9+
10+
func `if`<T>(true value: @autoclosure () throws -> T?) rethrows -> T? {
11+
if self {
12+
return try value()
13+
} else {
14+
return nil
15+
}
16+
}
17+
18+
func `if`<T>(false value: @autoclosure () throws -> T?) rethrows -> T? {
19+
if self {
20+
return nil
21+
} else {
22+
return try value()
23+
}
24+
}
25+
}

Sources/RecombinePackage/Middleware.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public struct Middleware<State, RawAction, RefinedAction> {
1616

1717
/// Create a `Middleware` out of multiple other `Middleware`.
1818
public init(_ middlewares: Self...) {
19-
self = middlewares.reduce(.init()) { $0.concat($1) }
19+
self = middlewares.reduce(.init()) { $0.appending($1) }
2020
}
2121

2222
/// Initialises the middleware with a transformative function.
@@ -28,7 +28,7 @@ public struct Middleware<State, RawAction, RefinedAction> {
2828
}
2929

3030
/// Concatenates the transform function of the passed `Middleware` onto the callee's transform.
31-
public func concat(_ other: Self) -> Self {
31+
public func appending(_ other: Self) -> Self {
3232
.init { state, action, dispatch in
3333
self.transform(state, action, dispatch).flatMap {
3434
other.transform(state, $0, dispatch)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
public struct SideEffect<RefinedAction> {
2+
public typealias Function = ([RefinedAction]) -> Void
3+
internal let closure: Function
4+
5+
/// Create a passthrough `SideEffect`.
6+
public init() {
7+
closure = { _ in }
8+
}
9+
10+
/// Create a `SideEffect` out of multiple other `SideEffect`s.
11+
public init(_ effects: Self...) {
12+
self = effects.reduce(.init()) { $0.appending($1) }
13+
}
14+
15+
/// Initialises the `SideEffect` with a transformative function.
16+
/// - parameter closure: The function that receives actions.
17+
public init(
18+
_ closure: @escaping ([RefinedAction]) -> Void
19+
) {
20+
self.closure = closure
21+
}
22+
23+
/// Creates a `SideEffect` that will run both the callee and caller's closures when run.
24+
public func appending(_ other: Self) -> Self {
25+
.init { actions in
26+
self.closure(actions)
27+
other.closure(actions)
28+
}
29+
}
30+
}
Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,54 @@
11
public struct ActionLens<RawAction, BaseRefinedAction, SubRefinedAction> {
2-
let dispatchFunction: (ActionStrata<[RawAction], [SubRefinedAction]>) -> Void
2+
public typealias Action = ActionStrata<[RawAction], [SubRefinedAction]>
3+
let dispatchFunction: (Bool, Bool, [Action]) -> Void
34

4-
public func callAsFunction<S: Sequence>(actions: S) where S.Element == SubRefinedAction {
5-
dispatchFunction(.refined(.init(actions)))
5+
public func callAsFunction<S: Sequence>(
6+
serially: Bool = false,
7+
collect: Bool = false,
8+
actions: S
9+
) where S.Element == Action {
10+
dispatchFunction(serially, collect, .init(actions))
611
}
712

8-
public func callAsFunction<S: Sequence>(actions: S) where S.Element == RawAction {
9-
dispatchFunction(.raw(.init(actions)))
13+
public func callAsFunction<S: Sequence>(
14+
serially: Bool = false,
15+
collect: Bool = false,
16+
refined actions: S
17+
) where S.Element == SubRefinedAction {
18+
dispatchFunction(serially, collect, [.refined(.init(actions))])
19+
}
20+
21+
public func callAsFunction<S: Sequence>(
22+
serially: Bool = false,
23+
collect: Bool = false,
24+
raw actions: S
25+
) where S.Element == RawAction {
26+
dispatchFunction(serially, collect, [.raw(.init(actions))])
1027
}
1128
}
1229

1330
public extension ActionLens {
14-
func callAsFunction(actions: SubRefinedAction...) {
15-
dispatchFunction(.refined(actions))
31+
func callAsFunction(
32+
serially: Bool = false,
33+
collect: Bool = false,
34+
actions: Action...
35+
) {
36+
callAsFunction(serially: serially, collect: collect, actions: actions)
37+
}
38+
39+
func callAsFunction(
40+
serially: Bool = false,
41+
collect: Bool = false,
42+
refined actions: SubRefinedAction...
43+
) {
44+
callAsFunction(serially: serially, collect: collect, refined: actions)
1645
}
1746

18-
func callAsFunction(actions: RawAction...) {
19-
dispatchFunction(.raw(actions))
47+
func callAsFunction(
48+
serially: Bool = false,
49+
collect: Bool = false,
50+
raw actions: RawAction...
51+
) {
52+
callAsFunction(serially: serially, collect: collect, raw: actions)
2053
}
2154
}

Sources/RecombinePackage/Store/BaseStore.swift

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class BaseStore<State: Equatable, RawAction, RefinedAction>: StoreProtoco
2727
reducer: R,
2828
middleware: Middleware<State, RawAction, RefinedAction> = .init(),
2929
thunk: Thunk<State, RawAction, RefinedAction> = .init { _, _ in Empty() },
30+
sideEffect: SideEffect<RefinedAction> = .init(),
3031
publishOn scheduler: S
3132
) where R.State == State, R.Action == RefinedAction {
3233
self.state = state
@@ -72,6 +73,9 @@ public class BaseStore<State: Equatable, RawAction, RefinedAction>: StoreProtoco
7273
group?.enter()
7374
DispatchQueue.global().async {
7475
self._postMiddlewareRefinedActions
76+
.handleEvents(receiveOutput: {
77+
sideEffect.closure($0)
78+
})
7579
.scan(state) { state, actions in
7680
actions.reduce(state, reducer.reduce)
7781
}
@@ -123,12 +127,17 @@ public class BaseStore<State: Equatable, RawAction, RefinedAction>: StoreProtoco
123127
_actionsPairedWithState.eraseToAnyPublisher()
124128
}
125129

130+
/// Dispatch actions to the store.
131+
///
132+
/// - parameter serially: Whether to resolve the actions concurrently or serially.
133+
/// - parameter collect: Whether to collect all refined actions and send them when finished, or send them as they are resolved.
134+
/// - parameter actions: The actions to be sent.
126135
open func dispatch<S: Sequence>(
127136
serially: Bool = false,
128137
collect: Bool = false,
129138
actions: S
130139
) where S.Element == Action {
131-
let maxPublishers: Subscribers.Demand = serially ? .max(1) : .unlimited
140+
let maxPublishers: Subscribers.Demand = serially.if(true: .max(1), false: .unlimited)
132141
weak var `self` = self
133142

134143
func recurse(actions: Action) -> AnyPublisher<[RefinedAction], Never> {
@@ -152,23 +161,19 @@ public class BaseStore<State: Equatable, RawAction, RefinedAction>: StoreProtoco
152161
maxPublishers: maxPublishers,
153162
recurse(actions:)
154163
)
155-
let transformed: AnyPublisher<[RefinedAction], Never>
156164

157-
if collect {
158-
transformed = recursed
165+
collect.if(
166+
true: recursed
159167
.collect()
160168
.map { $0.flatMap { $0 } }
169+
.eraseToAnyPublisher(),
170+
false: recursed
161171
.eraseToAnyPublisher()
162-
} else {
163-
transformed = recursed
164-
.eraseToAnyPublisher()
172+
)
173+
.sink {
174+
self?._preMiddlewareRefinedActions.send($0)
165175
}
166-
167-
transformed
168-
.sink {
169-
self?._preMiddlewareRefinedActions.send($0)
170-
}
171-
.store(in: &cancellables)
176+
.store(in: &cancellables)
172177
}
173178

174179
open func injectBypassingMiddleware<S: Sequence>(actions: S) where S.Element == RefinedAction {

Sources/RecombinePackage/Store/StoreProtocol.swift

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,19 @@ public extension StoreProtocol {
7979
BaseRefinedAction,
8080
SubRefinedAction
8181
> {
82-
ActionLens {
83-
switch $0 {
84-
case let .raw(actions):
85-
self.underlying.dispatch(raw: actions)
86-
case let .refined(actions):
87-
self.underlying.dispatch(refined: actions.map(self.actionPromotion))
88-
}
82+
ActionLens { [underlying, actionPromotion] serially, collect, actions in
83+
underlying.dispatch(
84+
serially: serially,
85+
collect: collect,
86+
actions: actions.map {
87+
switch $0 {
88+
case let .refined(actions):
89+
return .refined(actions.map(actionPromotion))
90+
case let .raw(actions):
91+
return .raw(actions)
92+
}
93+
}
94+
)
8995
}
9096
}
9197
}

Tests/RecombineTests/StoreMiddlewareTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class StoreMiddlewareTests: XCTestCase {
1212
let store = BaseStore(
1313
state: TestFakes.StringTest.State(),
1414
reducer: TestFakes.StringTest.reducer,
15-
middleware: firstMiddleware.concat(secondMiddleware),
15+
middleware: firstMiddleware.appending(secondMiddleware),
1616
thunk: .init(),
1717
publishOn: ImmediateScheduler.shared
1818
)

Tests/RecombineTests/StoreTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class DeInitStore<State: Equatable>: BaseStore<State, TestFakes.SetAction, TestF
9696
reducer: R,
9797
middleware _: Middleware<State, TestFakes.SetAction, TestFakes.SetAction> = .init(),
9898
thunk: Thunk<State, TestFakes.SetAction, TestFakes.SetAction> = .init(),
99+
sideEffect _: SideEffect<TestFakes.SetAction> = .init(),
99100
publishOn scheduler: S
100101
) where State == R.State, TestFakes.SetAction == R.Action, S: Scheduler, R: Reducer {
101102
super.init(

0 commit comments

Comments
 (0)