About ReSwift

ReSwift relies on a few principles:

  • The Store stores your entire app state in the form of a single data structure. This state can only be modified by dispatching Actions to the store. Whenever the state in the store changes, the store will notify all observers.
  • Actions are a declarative way of describing a state change. Actions don’t contain any code, they are consumed by the store and forwarded to reducers. Reducers will handle the actions by implementing a different state change for each action.
  • Reducers provide pure functions, that based on the current action and the current app state, create a new app state

For a very simple app, that maintains a counter that can be increased and decreased, you can define the app state as following:

struct AppState: StateType {
    var counter: Int = 0
}

You would also define two actions, one for increasing and one for decreasing the counter. In the Getting Started Guide you can find out how to construct complex actions. For the simple actions in this example we can define empty structs that conform to action:

struct CounterActionIncrease: Action {}
struct CounterActionDecrease: Action {}

Your reducer needs to respond to these different action types, that can be done by switching over the type of action:

func counterReducer(action: Action, state: AppState?) -> AppState {
    var state = state ?? AppState()

    switch action {
    case _ as CounterActionIncrease:
        state.counter += 1
    case _ as CounterActionDecrease:
        state.counter -= 1
    default:
        break
    }

    return state
}

In order to have a predictable app state, it is important that the reducer is always free of side effects, it receives the current app state and an action and returns the new app state.

To maintain our state and delegate the actions to the reducers, we need a store. Let’s call it mainStore and define it as a global constant, for example in the app delegate file:

let mainStore = Store<AppState>(
    reducer: counterReducer,
    state: nil
)

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    [...]
}

Lastly, your view layer, in this case a view controller, needs to tie into this system by subscribing to store updates and emitting actions whenever the app state needs to be changed:

class CounterViewController: UIViewController, StoreSubscriber {

    @IBOutlet var counterLabel: UILabel!

    override func viewWillAppear(_ animated: Bool) {
        mainStore.subscribe(self)
    }

    override func viewWillDisappear(_ animated: Bool) {
        mainStore.unsubscribe(self)
    }

    func newState(state: AppState) {
        counterLabel.text = "\(state.counter)"
    }

    @IBAction func increaseButtonTapped(_ sender: UIButton) {
        mainStore.dispatch(
            CounterActionIncrease()
        )
    }

    @IBAction func decreaseButtonTapped(_ sender: UIButton) {
        mainStore.dispatch(
            CounterActionDecrease()
        )
    }

}

The newState method will be called by the Store whenever a new app state is available, this is where we need to adjust our view to reflect the latest app state.

Button taps result in dispatched actions that will be handled by the store and its reducers, resulting in a new app state.

This is a very basic example that only shows a subset of ReSwift’s features, read the Getting Started Guide to see how you can build entire apps with this architecture. For a complete implementation of this example see the CounterExample project.

You can also watch this talk on the motivation behind ReSwift.