Seda is a Redux + Command architecture framework for SwiftUI and UIKit.
Seda's architecture is a composite of the Redux and the Command.
In general, Redux's reducer returns only a state. But Seda's reducer returns the state and the 'Command' as follows.
func reducer() -> Reducer<AppState> {
return { action, state in
var state = state
var command = Command.none
...
switch action {
case Foo.asyncAction:
command = Command.ofAsyncAction { fullfill in
asyncFunc { value in fulfill(Foo.updateValue(value)) }
}
case Foo.updateValue(let value):
state.value = value
...
}
...
return (state, command)
}
}
The Command manages async and side effect tasks. The tasks wrapped by the command don't leak out to reducer that should be a pure function. Because of this, we become able to write async and side effect tasks in a reducer in safety.
On Xcode, select 'File' menu -> 'Swift Packages' -> 'Add Package Dependency...'.
In Podfile,
pod 'Seda', '~> 0.1'
And execute pod install
.
Essentially, Seda is almost Redux.
Define Redux's state. State must adopt the StateType protocol.
struct State: StateType {
var count: Int = 0
}
Define Redux's action. Action must adopt the ActionType protocol.
enum CountAction: ActionType {
case step(Step)
case stepDelayed(Step)
}
Seda's reducer is different a little from Redux's reducer.
Reducer type must be Reducer<State>.
Reducer is called with action and state. And reducer returns state and command.
typealias CounterReducer = Reducer<State>
func counterReducer() -> CounterReducer {
return { action, state in
var state = state
var command = Command.none
switch action {
case CountAction.step(let step):
state.count += step.count
state.history.append(step)
case CountAction.stepDelayed(let step):
command = .ofAsyncAction { fulfill in
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(2)) {
fulfill(CountAction.step(step))
}
}
default: ()
}
return (state, command)
}
}
Define SwiftUI view. View must adopt the StatefulView protocol.
struct CounterView: StatefulView {
@EnvironmentObject var store: Store<State>
var body: some View {
...
}
}
let store = Store<State>(reducer: counterReducer(), state: State())
let counterView = CounterView().environmentObject(store)