About ReactiveReSwift
ReactiveReSwift 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, one that maintains a counter that can be increased and decreased, you can define the app state as following:
struct AppState {
let counter: Int
}
You would also define two actions, one for increasing and one for decreasing the counter. For the simple actions in this example we can use an enum that conforms to action:
enum AppAction: Action {
case increase
case decrease
}
Your reducer needs to respond to these different actions, that can be done by switching over the value of action:
let appReducer: Reducer<AppState> = { action, state in
switch action as? AppAction {
case .increase?:
return AppState(counter: state.counter + 1)
case .decrease?:
return AppState(counter: state.counter - 1)
default:
return state
}
}
A single Reducer
should only deal with a single field of the state struct. You can nest multiple reducers within your main reducer to provide separation of concerns.
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:
fileprivate let initialState = AppState(counter: 0)
let mainStore = Store(
reducer: appReducer,
observable: ObservableProperty(initialState)
)
@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 {
private let disposeBag = SubscriptionReferenceBag()
@IBOutlet var counterLabel: UILabel!
override func viewDidLoad() {
disposeBag += mainStore.observable.subscribe { [weak self] state in
self?.counterLabel.text = "\(state.counter)"
}
}
@IBAction func increaseButtonTapped(sender: UIButton) {
mainStore.dispatch(
AppState.CounterActionIncrease
)
}
@IBAction func decreaseButtonTapped(sender: UIButton) {
mainStore.dispatch(
AppState.CounterActionDecrease
)
}
}
The mainStore.observable.subscribe
block will be called by the ObservableStore
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 ReactiveReSwift’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 ReactiveCounterExample project.
You can also watch this talk on the motivation behind ReSwift.