Sadā
Sadā
Jun 18 · 5 min read

State and all the binding properties make SwiftUI so much fun by taking away a lot of boilerplate code. However, over a period of time when the app code base grows, managing those properties could become a headache.

Even though if we decide to go with States for local data dependency and Binding/ObjectBinding for External data dependency, having a single source of truth for all the data that views consume would help us in many ways. Following are some of the benefits. —

  1. Predictable states by keeping centralized state management.
  2. Easy to test as data/state update become independent.
  3. State/Data manipulation logic would be moved out of views, allowing views to focus on displaying the data and emitting the events when users interact.
  4. Of course one source of truth for the whole application

Let’s understand this problem with an analogy. Banking system — Withdraw money flow. Banks keep cash in the vault, and if I need cash I have to go through the bank cashier. But wait, why can’t I directly go to the vault? What is cashier doing in between?

Well, the cashier is helping the bank keep my money and importantly all other account holder money safe by verifying various things before he gives me cash.

Now, relate cash/vault with our states/data and account-holders with various state-consumers(views) and think what happens if we don’t keep someone like cashier to protect our states from unprotected access? Of course, anyone can access the states and alter them and we‘d lose track of who is updating what, and eventually, state of the app could become unpredictable.

To be clear, all we want is — In order to keep states safe all the views must go through a process with one intent whenever, views want to modify/reset states, that way we can define and control the process based on the intent.

We can call view’s intents are Actions and the process that changes the states based on actions are Reducers. When views need a new state, they go to a Reducer with an action asking new states. Reducers take the current state and execute the action on the current state and return a new state.

In fact, we’ve seen Reducers like functions in Swift. Array structure hasreduce(_:_) function. You can read more here. Below is an example of what reduce does.

// 1
let numbers = [1, 2, 3, 4]
// 2
let numberSum = numbers.reduce(0, { x, y in
x + y
})
// numberSum == 10
  1. Creating an Array of integers.
  2. Calling reduce function to do the sum of integers in the numbers array. It takes an initial value to start with and iterates over numbers to add them up and finally returns a number that is a sum.

Let’s modify the above code a bit, so it becomes scalable.

let numbers = [1, 2, 3, 4]// 1
let sum: (Int, Int) -> Int = {x, y in x + y }
let multiply: (Int, Int) -> Int = {x, y in x * y }
// 2
enum Action {
case sum
case multiply
}
let action = Action.sum// 3
switch action {
case .sum: return numbers.reduce(0, sum)
case .multiply: return numbers.reduce(0, multiply)
}
  1. sum and multiply are 2 closures that take 2 parameters and return an integer.
  2. An enumerator that holds available actions
  3. Handle the action by calling reduce on the number using the closure from step#1

Now, we can apply the same concepts to manage the states. Let’s code-up a Todos app. Below are basic states and actions for a todo app

  1. States — Todo items. An array of strings
  2. Actions — Add an item, Delete an item, Mark an item as done, etc
  3. Reducer — Call it TodoReducer, takes an action and modifies todo items array and return a same to the caller.

Below is TodoItem model to hold todo info. Few props and 2 methods to delete and mark a todo as completed.

And here’s the reducer and available actions for views on todo items.

  1. An enum to list the available actions
  2. A structure whose job to provide a reducer for todo items array.
  3. Reducer function that takes an old state that is a list of Todos and returns a list.

Now, we have actions and reducer defined. How do views interact with these or make use of what is defined? For that, we’d need a Store to hold the current state and importantly allow views to talk whenever state changes.

  1. Store class. It’s a class because we want it to hold TodoItem array and serve as a single source of truth for all the views.
  2. didChange to conform with BindingObject the protocol. This is SwiftUI way of binding external data with views that way, changes in the state can be easily communicated with views.
  3. Only non-private property that views can use to access the current state
  4. Store initializer — sets the initial state to the currentState and reducer .
  5. dispatch(action:) is a very important function. All the views must call this function to perform any action on the state. This is only gate to update the state.
  6. Once the new state is set, we let the views know that state has changed so that views can refresh the views data.

Finally, let’s see how SwiftUI Views can consume this.

Whoa! That’s huge. Let’s break it down.

  1. ContentView is the base view for the todo app.
  2. store property initiates Store with an initial state. Yes. The app starts with a todo item called Try SwiftUI! .
  3. Body of content view, calls another TodoList view and importantly passed the store object as an environment object, so that, all the children views can access it using @EnvironmentObject. Yes, of course, this is one way to pass the data down to children, you could use @Binding as well.
  4. Inside TodoList view, store property gets access to our store.
  5. Inside List view ForEach accesses the store property like any other property.
  6. An important thing to note here— when we need to delete an item, we are letting know the store by calling dispatch. That’s it, the store takes of the calling the reducer and once the new state is available, the views will be notified and refreshed automatically.
  7. Similarly, calling dispatch to perform add actions.

Final product —

Conclusion —

Although the Todo app isn’t probably a great example to explain all the benefits of managing the states using Redux concepts, separating the state/data update logic itself would help manage code and test some of the logic independently.

PS: I’m working to creating a library for SwiftUI Redux. Here is the link. At this moment, it contains very minimal classes to get started with Redux. If you think, it makes to have a library for Redux, feel free to contribute. Thanks

References —

https://www.freecodecamp.org/news/understanding-redux-the-worlds-easiest-guide-to-beginning-redux-c695f45546f6/

https://developer.apple.com/documentation/swift/array/2298686-reduce

The Startup

Medium's largest active publication, followed by +492K people. Follow to join our community.

Sadā

Written by

Sadā

The Startup

Medium's largest active publication, followed by +492K people. Follow to join our community.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade