Skip to content

Commit b7ce123

Browse files
authored
Merge pull request #3 from jarsen/commands
Add Commands!
2 parents 01c87f8 + 1e47af3 commit b7ce123

File tree

2 files changed

+75
-40
lines changed

2 files changed

+75
-40
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,31 @@ extension ViewController: Subscriber {
130130

131131
By subscribing and subscribing in `viewDidAppear`/`viewDidDisappear` respectively, we ensure that whenever this view controller is visible it is up to date with the latest application state. Upon initial subscription, the reactor will send the latest state to the subscriber's `update` function. Button presses forward events back to the reactor, which will then update the state and result in subsequent calls to `update`. (note: the Reactor always dispatches back to the main thread when it updates subscribers, so it is safe to perform UI updates in `update`.)
132132

133+
## Commands
134+
135+
Sometimes you want to fire an `Event` at a later point, for example after a network request, database query, or other asynchronous operation. In these cases, `Command` helps you interact with the `Reactor` in a safe and consistent way.
136+
137+
```swift
138+
struct CreatePlayer: Command {
139+
var session = URLSession.shared
140+
var player: Player
141+
142+
func execute(state: RPGState, reactor: Reactor<RPGState>) {
143+
let task = session.dataTask(with: player.createRequest()) { data, response, error in
144+
// handle response appropriately
145+
// then fire an update back to the reactor
146+
reactor.fire(event: AddPlayer(player: player))
147+
}
148+
task.resume()
149+
}
150+
}
151+
152+
// to fire a command
153+
reactor.fire(command: CreatePlayer(player: myNewPlayer))
154+
```
155+
156+
Commands get a copy of the current state, and a reference to the Reactor so they can fire Events as necessary.
157+
133158
## Middleware
134159

135160
Sometimes you want to do something with an event besides just update application state. This is where `Middleware` comes into play. When you create a `Reactor`, along with the initial state, you may pass in an array of middleware. Each middleware gets called every time an event is passed in. Middleware is not allowed to mutate the state, but it does get a copy of the state along with the event. Middleware makes it easy to add things like logging, analytics, and error handling to an application.

Sources/Reactor.swift

Lines changed: 50 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,42 @@
11
import Foundation
22

3-
public protocol Event {}
3+
4+
5+
// MARK: - State
46

57
public protocol State {
68
mutating func react(to event: Event)
79
}
810

11+
12+
// MARK: - Events
13+
14+
public protocol Event {}
15+
16+
17+
// MARK: - Commands
18+
19+
20+
public protocol Command {
21+
associatedtype StateType: State
22+
func execute(state: StateType, reactor: Reactor<StateType>)
23+
}
24+
25+
26+
// MARK: - Middlewares
27+
928
public protocol AnyMiddleware {
1029
func _process(event: Event, state: Any)
1130
}
1231

1332
public protocol Middleware: AnyMiddleware {
14-
associatedtype State
15-
func process(event: Event, state: State)
33+
associatedtype StateType
34+
func process(event: Event, state: StateType)
1635
}
1736

1837
extension Middleware {
1938
public func _process(event: Event, state: Any) {
20-
if let state = state as? State {
39+
if let state = state as? StateType {
2140
process(event: event, state: state)
2241
}
2342
}
@@ -27,48 +46,40 @@ public struct Middlewares<ReactorState: State> {
2746
private(set) var middleware: AnyMiddleware
2847
}
2948

49+
50+
// MARK: - Subscribers
51+
3052
public protocol AnySubscriber: class {
3153
func _update(with state: Any)
3254
}
3355

3456
public protocol Subscriber: AnySubscriber {
35-
associatedtype State
36-
func update(with state: State)
57+
associatedtype StateType
58+
func update(with state: StateType)
3759
}
3860

3961
extension Subscriber {
4062
public func _update(with state: Any) {
41-
if let state = state as? State {
63+
if let state = state as? StateType {
4264
update(with: state)
4365
}
4466
}
4567
}
4668

47-
public struct Subscription<ReactorState: State> {
69+
public struct Subscription<StateType: State> {
4870
private(set) weak var subscriber: AnySubscriber? = nil
49-
let selector: ((ReactorState) -> Any)?
71+
let selector: ((StateType) -> Any)?
5072
}
5173

5274

53-
public class Reactor<ReactorState: State> {
54-
55-
/**
56-
An `EventEmitter` is a function that takes the state and a reference
57-
to the reactor and optionally returns an `Event` that will be immediately
58-
executed. An `EventEmitter` may also use its reactor reference to perform
59-
events at a later time, for example an async callback.
60-
*/
61-
public typealias EventEmitter = (ReactorState, Reactor<ReactorState>) -> Event?
62-
63-
// MARK: - Properties
64-
65-
private var subscriptions = [Subscription<ReactorState>]()
66-
private var middlewares = [Middlewares<ReactorState>]()
67-
68-
69-
// MARK: - State
75+
76+
// MARK: - Reactor
77+
78+
public class Reactor<StateType: State> {
7079

71-
private (set) var state: ReactorState {
80+
private var subscriptions = [Subscription<StateType>]()
81+
private var middlewares = [Middlewares<StateType>]()
82+
private (set) var state: StateType {
7283
didSet {
7384
subscriptions = subscriptions.filter { $0.subscriber != nil }
7485
DispatchQueue.main.async {
@@ -79,23 +90,16 @@ public class Reactor<ReactorState: State> {
7990
}
8091
}
8192

82-
private func publishStateChange(subscriber: AnySubscriber?, selector: ((ReactorState) -> Any)?) {
83-
if let selector = selector {
84-
subscriber?._update(with: selector(self.state))
85-
} else {
86-
subscriber?._update(with: self.state)
87-
}
88-
}
8993

90-
public init(state: ReactorState, middlewares: [AnyMiddleware] = []) {
94+
public init(state: StateType, middlewares: [AnyMiddleware] = []) {
9195
self.state = state
9296
self.middlewares = middlewares.map(Middlewares.init)
9397
}
9498

9599

96100
// MARK: - Subscriptions
97101

98-
public func add(subscriber: AnySubscriber, selector: ((ReactorState) -> Any)? = nil) {
102+
public func add(subscriber: AnySubscriber, selector: ((StateType) -> Any)? = nil) {
99103
guard !subscriptions.contains(where: {$0.subscriber === subscriber}) else { return }
100104
subscriptions.append(Subscription(subscriber: subscriber, selector: selector))
101105
publishStateChange(subscriber: subscriber, selector: selector)
@@ -105,17 +109,23 @@ public class Reactor<ReactorState: State> {
105109
subscriptions = subscriptions.filter { $0.subscriber !== subscriber }
106110
}
107111

112+
private func publishStateChange(subscriber: AnySubscriber?, selector: ((StateType) -> Any)?) {
113+
if let selector = selector {
114+
subscriber?._update(with: selector(self.state))
115+
} else {
116+
subscriber?._update(with: self.state)
117+
}
118+
}
119+
108120
// MARK: - Events
109121

110122
public func fire(event: Event) {
111123
state.react(to: event)
112124
middlewares.forEach { $0.middleware._process(event: event, state: state) }
113125
}
114126

115-
public func fire(emitter: EventEmitter) {
116-
if let event = emitter(state, self) {
117-
fire(event: event)
118-
}
127+
public func fire<C: Command>(command: C) where C.StateType == StateType {
128+
command.execute(state: state, reactor: self)
119129
}
120130

121131
}

0 commit comments

Comments
 (0)