Press enter or click to view image in full size

Before we start, what is Redux? From redux.js.org:
Redux is a predictable state container for JavaScript apps.
So if you are not a web developer, it’s totally normal that you don’t know what Redux is. But it’s really easy to understand and the concept can be applied to everywhere, including iOS development.
To summarise, your app has a store. In this store, you have your application state. From your views, you send actions to the store and reducers execute these actions. As state is changed by reducers, store notifies the view so that it can reflect changes on UI.
Core benefits:
However, Redux is very strict and not very easy to apply in iOS applications, mainly because of UIKit. I won’t go into details, but if you curious, I recommend you to watch
’s great talk on this topic.
MVVM is a lightweight, easy-to-adapt architecture, which is similar to MVC. However, it doesn’t really provide the benefits of Redux. So why not use some of the concepts? I will try to improve the communication between view controller and view model using the principles from Redux.
For more on MVVM:
’s post on iOS Architecture Patterns
Let’s implement a simple app, in which you list some movies in a table view and user can move them around, delete some or insert new ones.
First, define the state.
struct MoviesState {
var movies: [Movie] = []
var fetching: Bool = false
}VM will be our store, so keep the state in VM.
class MoviesViewModel {
private(set) var state = MoviesState()
}In Redux, reducers mutate the state using pure functions. To keep it simple, we can define reducers as mutating functions on MoviesState. But they should be dead-simple, sync and atomic functions that does only one job.
extension MoviesState {
mutating func reloadMovies(movies: [Movie]) {
self.movies = movies
}
}reloadMovies function will just set movies array in state struct and when the state changes, we should notify view controller so that it can update UI to reflect changes.
But how?
To be able to that, we should also model changes that can happen in our state, and feed VC with these change objects so that it can sync UI with current state.
Join Medium for free to get updates from this writer.
In our humble example, we can model changes with an enum like this:
extension MoviesState {
enum Change {
case none
case fetchStateChanged
case moviesChanged
}
}Then, all the mutating functions on state should return a Change object.
extension MoviesState {
func reloadMovies(…) -> Change {
...
return .moviesChanged
}
}Then, we need a way to propagate this change to VC. A simple handler block would do fine.
class MoviesViewModel {
...
var stateChangeHandler: ((MoviesState.Change) -> Void)?
}Finally in VC, all we need to do is:
class MoviesViewController { override func viewDidLoad() { super.viewDidLoad() model.stateChangeHandler = { change in switch change {
case .none:
break // no change
case .moviesChanged:
tableView.reloadData()
case .fetchStateChanged:
setLoadingViewVisible(model.fetching)
}
} model.fetchMovies()
}
}
You might have noticed that fetchMovies is an async call, but it doesn’t have a callback. Because you don’t need it. That call will make some changes on state, over time. And every time it happens, stateChangeHandler block will be called with that exact change in state.
This is how fetchMovies call should look like:
func fetchMovies() {let fetchStateChange = state.setFetching(true)
stateChangeHandler?(fetchStateChange)API.fetchMovies { movies in
let reloadChange = state.reloadMovies(movies)
stateChangeHandler?(reloadChange) let fetchStateChange = state.setFetching(false)
stateChangeHandler?(fetchStateChange)
}
}
We have used store, state and reducers from Redux. How about actions? Actually, we already used it. Since we don’t talk to MoviesState directly, all methods defined on VM can be seen as Redux actions that initiate a state change.