Rx State Reducer
February 23, 2019 · View on GitHub
This repository demonstrates a pattern of asynchronous state management based on ideas of reactive programming, state reducer and unidirectional data flow. This is not a library, because you don’t need yet another library to implement this.
Running
- Install Carthage
brew install carthage
- Build the dependencies
carthage bootstrap --platform iOS
Problem
Asynchronous state management is a non-trivial modelling exercise and requires a reliable approach to keep it concise and maintainable.
Most trivial applications start without any explicit state management approach. However, things quickly get out of hand when the number of states in which a system can reside starts to grow.
Finite-State Machine and State Pattern can help manage synchronous state transitions, but are not designed to handle asynchronous behaviour.
Unless you can model your entire system synchronously, a single asynchronous source breaks imperative programming.
-- Jake Wharton
Solution
Explicitly define the state of a system, then use a reducer function to compute a new state based on the previous state. Use reactive streams to handle asynchronous tasks and make data flow in one direction.
- An event is received and transformed to an
Action. - The
Actionis handled and transformed toMutation. This transformation is needed whenActionsrequire asynchronous handling. SuchActionsproduce multipleMutations(e.g., (1)loading, (2)loaded) - Each
Mutationis passed to theReducerfunction, which uses it to transform the currentStateto a newState.
Where:
Action- an interpretation of the system or user eventMutation- a result of theActionhandlingState- a model of the system or use caseReducer- a pure function which takes aStateand aMutationand produces a newState

Example
Viewreceives a user event.Viewtransforms the event to anAction.Viewpropagates theActiontoInteractor.Interactorhandles theActionand transforms it toMutations.Interactoruses eachMutationto transform the currentStateto a newState.Interactorpropagates the newStatetoViewViewupdates itself using the newState. The unidirectional feedback loop is now complete.
Given that actions is Observable<Action>, states is Observable<State>, the unidirectional feedback loop is expressed as:
states = actions.flatMap(handleAction).scan(State.initial, accumulator: reduce)
Action and Mutation are expressed as an enum:
enum Action {
case reload
}
enum Mutation {
case loading
case records([Record])
case failure(Error)
}
State is expressed as a struct (if there is a strict number of finite states, then enum):
struct State {
var records: [Record]
var isLoading: Bool
var error: Error?
static var initial = State(records: [], isLoading: false, error: nil)
}
Reducer is a pure function:
func reduce(state: State, mutation: Mutation) -> State {
var state = state
switch mutation {
case let records(records):
state.records = records
case let failure(error):
...
case loading:
...
}
return state
}
The function handleAction is asynchronous:
func handleAction(_ action: Action) -> Observable<Mutation> {
switch action {
case reload:
return gateway.fetchRecords()
.map(Mutation.records)
.startWith(Mutation.loading)
.catchError({ .just(Mutation.failure(\$0)) })
}
}
State introspection
Check branch state-feedback
To introspect State during handleAction it can be fed back into the loop:
func start(actions: Observable<Action>) -> Observable<State> {
let states = BehaviorRelay(value: State.initial)
return actions
.withLatestFrom(states, resultSelector: { (\$0, \$1) })
.flatMap(handleAction)
.scan(states.value, accumulator: reduce)
.do(onNext: states.accept)
}
The function handleAction will now take two parameters:
func handleAction(_ action: Action, state: State) -> Observable<Mutation> {
...
}