Chapters

Hide chapters

Advanced iOS App Architecture

Fourth Edition · iOS 15 · Swift 5.5 · Xcode 13.2

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

Section I

Section 1: 9 chapters
Show chapters Hide chapters

6. Architecture: Redux
Written by René Cacheaux & Josh Berlin

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

At Facebook, some years ago, a bug in the desktop web app sparked a new architecture. The app presented the unread count of messages from Messenger in several views at once, not always presenting the same amount of unread messages. This could get out of sync and report different numbers, so the app looked broken. Facebook needed a way to guarantee data consistency and, out of this problem, a new unidirectional architecture was born — Flux.

After Facebook moved to a Flux based architecture, views that showed the unread message count got data from the same container. This new architecture fixed a lot of these kinds of bugs.

Flux is a pattern, though, not a framework. In 2015, Dan Abramov and Andrew Clark created Redux as a JavaScript implementation of a Flux inspired architecture. Since then, others have created Redux implementations in languages such as Swift and Kotlin.

What is Redux?

Redux is an architecture in which all of your app’s state lives in one container. The only way to change state is to create a new state based on the current state and a requested change.

The Store holds all of your app’s state.

An Action is immutable data that describes a state change.

A Reducer changes the app’s state using the current state and an action.

Store

The Redux store contains all state that drives the app’s user interface. Think of the store as a living snapshot of your app. Anytime its state changes, the user interface updates to reflect the new state.

Types of state

A Store contains data that represents an app’s user interface (UI). Here are some examples:

Derived values

The store doesn’t contain larger files, such as images or videos. Instead, it contains file URLs pointing to media on disk.

Modeling view state

In a Redux architecture, views store no state. They only listen and react to state changes. So, any state that changes how the view behaves lives in the store. Stores consist of immutable value types. When data in the store changes, views get a new, immutable state to re-render the user interface.

Sign-in screen

Onboarding displays a welcome screen where you can navigate to the sign-in or sign-up screens. The app state determines which screen is currently shown to the user. When the app state changes, the app presents a new screen to the user.

Loading and rendering initial state

Koober has two high-level app states:

Subscription

For a view to render, it subscribes to changes in the store. Each time state changes, the view gets wholesale changes containing the entire state — there is no middle ground. This is unlike MVVM, where you manipulate one property at a time.

Responding to user interactions

Actions are immutable data that describe a state change. You create actions when the user interacts with the user interface.

Threading

In Redux, it’s important to run all the reducers on the same thread. It doesn’t have to be the main thread, but the same serial queue.

Performing side effects

Side effects are any non-pure functions. Any time you call a function that can return a different value given the same inputs is a side effect. Pure functions are deterministic. Given the same inputs, the function always has the same outputs.

Rendering updates

Redux is a “reactive” architecture. The word “reactive” is thrown around a lot these days. In Redux, “reactive” means the view receives updated state via subscriptions, and it “reacts” to the updates. Views never ask the store for the current state; they only update when the store fires an update that data changed.

Diffing

Each time a view receives new state via subscription, it gets the whole state. The view needs to figure out what changed and then properly render the update.

Example: Onboarding to signed-in

Koober has two high-level app states:

Example: Signing in

The sign-in screen contains a Username / Email text field, Password text field and a Sign In button. Tapping the Sign In button signs you in using the username and password inputs.

Applying theory to iOS apps

If we had to guess, you’re probably ready for some Kangaroo-filled code examples after all that theory! Let’s dive into the code and see how Redux actually works in practice.

Redux in iOS apps

ReSwift and Katana are the two main Swift Redux implementations. Both have a strong following on GitHub and are great choices for your Redux library. Redux is a simple concept, and you could write your own Redux library. All the Redux libraries are super small by design. Either way, use of a library is recommended.

Building a view

Before you can hop on a Kangaroo around Sydney, you have to sign in to Koober. You sign in to the app in the sign-in screen, which contains an Email field, Password field and a Sign In button.

View controller

The SignInViewController configures the SignInRootView and observes store state changes.

Sign-in view state

Open, SignInViewControllerState.swift inside KooberKit:

Sign-in user interactions

The SignInUserInteractions protocol describes possible user interactions in the sign-in screen:

App state

SignInViewControllerState describes the sign-in screen in isolation. But the state is part of a larger state tree.

Equatable state models

To prevent duplicate calls, make your state models Equatable. Otherwise, multiple calls to UI methods could occur. For example, you could present a view controller over and over again — not a great user experience!

Using Combine to observe ReSwift

Koober abstracts the ReSwift dependency from all user interface code, including UIViewControllers and UIViews. This makes it easier to switch the Redux implementation down the road, since none of the user interface code needs to change. Koober still gets the benefits of ReSwift, though. It still dispatches actions and changes state in pure reducer functions. The difference is Combine drives the user interface updates instead of ReSwift store subscriptions.

ReSwift publishers

Instead of subscribing directly to the ReSwift state store, view controllers in Koober subscribe to Combine publishers created from the ReSwift store. For this to work, a Combine Subscription forwards the ReSwift store subscriber updates to Combine publisher subscribers:

Focusing the publisher

Each view only cares about a subset of the ReSwift store’s state tree. For example, the user profile screen displays user information and knows nothing about the map. There’s no point for the store to notify the profile screen when the user’s location changes or more Kangaroos become available for a ride.

Scoped state

When using enums to model app state, views might be observing state that goes out of scope. When an enum case changes, some part of the state tree goes away. For example, in the pick-me-up flow, there’s an enum for the step of the ride request the user engaged in. As the user moves through the cases, anything observing an associated value in a changed case goes out of scope. In practice, you don’t ever want to observe an out-of-scope state. Going out of scope means a view controller is living longer than you designed it to live for.

User session persistence

Koober persists the user session on disk between sessions. On launch, the app reads the user session from persistence. If it exists, the user is authenticated and can request rides. If it doesn’t exist, the user must go through the onboarding flow.

Responding to user interaction

View controllers in Koober declare all possible user interactions in a user interactions protocol. The implementation of the class gets injected on initialization. Most user interactions result in a modification to the store. You can think of the user interaction objects like view models in MVVM.

Rendering updates

Actions describe a state change. Let’s look at what makes up an action:

Communicating amongst views

In Redux, there is no direct communication between views. They observe state from the same store, so one view controller can affect another by dispatching an action. The reducer updating the store can change state another view controller is observing.

Pick-me-up screen

The PickMeUpViewController contains the meat of the Koober app. It displays the map, the Where To? button and the ride-option picker.

Selecting a ride option

Koober has a wide variety of ride option types to choose from:

Pros and cons of Redux

Pros of Redux

  1. Redux scales well as your application grows — if you follow best practices. Separate your Redux store state into sub-states and only observe partial state in your view controllers.
  2. Descriptive state changes are all contained in reducers. Any developer can read through your reducer functions to understand all state changes in the app.
  3. The store is the single source of truth for your entire app. If data changes in the store, the change propagates to all subscribers.
  4. Data consistency across screens is good for iPad apps and other apps that display the same data in multiple places at the same time.
  5. Reducers are pure functions — they are easy to test.
  6. Redux architecture, overall, is easy to test. You can create a test case by putting the app in any app state you want, dispatch an action and test that the state changed correctly.
  7. Redux can help with state restoration by initializing the store with persisted state.
  8. It’s easy to observe what’s going on in your app because all the state is centralized to the store. You can easily record state changes for debugging.
  9. Redux is lightweight and a relatively simple high-level concept.
  10. Redux helps separate side effects from business logic.
  11. Redux embraces value types. State can’t change from underneath you.

Cons of Redux

  1. You need to touch multiple files to add new functionality.
  2. Requires a third-party library, but the library is very small.
  3. Model layer knows about the view hierarchy and is sensitive to user-interface changes.
  4. Redux can use more memory than other architectures since the store is always in memory.
  5. You need to be careful with performance because of possible frequent deep copies of the app state struct.
  6. Dispatching actions can result in infinite loops if you dispatch actions in response to state changes.
  7. Data modeling is hard. Benefits of Redux depend on having a good data model.
  8. It is designed to work with a declarative user interface framework like React. This can be awkward to apply to UIKit because UIKit is imperative. This isn’t a blocker, just that it’s not a natural fit.
  9. Since the entire app state is centralized, it’s possible to have reducers that depend on each other. That removes modularity and encapsulation of a model / screen / component’s state. So refactoring a component’s state type could cause complier issue elsewhere and this is not good. You won’t run into this if you organize your reducers to only know about a module’s state and no more. This is not constrained by the architecture, though, so it depends on everyone being aware.

Key points

Where to go from here?

Koober is meant to be a production app, and there’s lots more code in the sample project to explore.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now