Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Flux, Redux, and SwiftUI

Before we dive into some code, it’s important to begin with some background, philosophy, and history. What did product engineering look like for the Apple Ecosystem before SwiftUI? How did other declarative UI frameworks and ecosystems manage shared mutable state at scale? What could we learn from other ecosystems that could influence how we think about our shared mutable state when building products for SwiftUI? It’s important to understand that the architecture we are proposing was not built in a vacuum. The code itself will be new, but the ideas behind the code have already proven themselves in the real world.

React

Facebook launched in 2004.1 By 2010, FB had grown to 500 million users.2 The previous year, FB reached another important milestone: profitability.3 To understand more the scale that engineers were shipping impact at during this era, FB was employing less than 500 engineers.

Historically, FB hired engineers as software generalists. Engineers were onboarded without being preallocated into one specific team or role. After onboarding, engineers quickly discovered that nothing was ever “complete” at FB. The company and products grew so quickly, engineers were encouraged — and were needed — to work on new products outside their speciality.

As FB (the company and the product) scaled, engineers building for front-end WWW began to realize their architecture was not scaling with them. At this point in time, FB front-end architecture was built on “classic” front-end engineering. Engineers used imperative logic to manipulate long-lived mutable view objects. Sometimes this was called “MVC”, sometimes it was called “MVVM”, and sometimes it was called “MVW” (Model-View-Whatever) or “Model-View-Asterisk”. These architectures all shared a common assumption that made it challenging to onboard new engineers and ship products at scale: views were mutable objects and engineers needed to use complex imperative logic to manage their state correctly.

Starting around 2011, a FB engineer named Jordan Walke began to “rethink best practices” about application architecture for front-end engineering. This new framework would become ReactJS. React gave engineers the tools to declare their view component tree without the need for imperative mutations directly on mutable view objects. React gave product engineers the tools to focus on “the what not the how”. For two years, FB products began to migrate to this new infra. React led to code that was faster to write and easier to maintain. After crossing the threshold of one-billion users in 2012,4 FB announced the ReactJS framework would be released to the community as open source in 2013.5

The early public demos of React gave engineers a battle-tested infra for declaring their user interface, but FB did not publicly make a strong value statement about how these engineers should manage shared mutable state at scale. While a declarative framework like React redefines the “Controller-View” relationship, React — as announced to the public — did not yet have a strong opinion about how to redefine the “Model-Controller” relationship.

Flux

What engineers outside FB did not know was that FB did have a new application architecture being developed internally alongside the React infra. During the two years React was being developed and scaled, a team of FB engineers led by Jing Chen also noticed that their architecture was not scaling as the company was growing. React focused its attention on redefining the programming model engineers use to build graphs of view components; this new team of engineers began to think about their data and their models with a similar philosophy.

At this time, conventional architectures were encouraging complex mutable state to be delivered to views through mutable objects: controllers or view models. Views were using imperative logic to mutate shared state directly: through a controller or on the model objects themselves. As the size of the product grew, the graph of relationships between controllers and models grew quadratically: it was out of control. One controller class would become so large it was slowing down engineers that needed to work on it. A temporary solution might have been to break this apart into “child” controllers, but the relationship graph between these controllers then grew quadratically. There was always going to be quadratic complexity; trying to refactor it out of one place just moved it somewhere else.

In addition, mutable model objects — where the state of these models could be directly mutated with imperative logic by the view objects — led to code that was difficult to reason about; as the size of the product scaled, engineers needed to know “global” context to make “local” changes. It was very easy for bugs to ship; an engineer might mutate state at some place in the view graph when another place in the view graph was not expecting, or prepared for, a mutation.

The next problem was bugs that looked like “race conditions” or “non-deterministic” chain reactions. Since mutable state was often being bound with two-directional data bindings that can both read from and write to an object, an engineer might mutate shared state in one part of their view graph, while a different part of the view graph is not only subscribing to listen for state mutations, but then making their own state mutations once they received that notification.

Code was complex, unpredictable, and difficult to make changes to. Parallel to the work the React team was doing to redefine the programming model product engineers used to build user interfaces, this new team began to redefine the programming model product engineers used to manage shared mutable state.

The architecture was called Flux, and shared a lot of philosophy with React. Flux gave product engineers tools to think about shared mutable state by moving away from an imperative programming model. Product engineers could declare actions and events as they happen from their view components and migrate the imperative logic to mutate state down into their model layer in Flux Stores. Product engineers no longer needed two-directional data bindings that read and wrote to shared state; the data flow became one-directional. Complex view controllers — or complex graphs of view controllers — started to become obsolete. Code became simple, predictable, and easy to make changes to.

Flux was built on JS, but drew a lot of influence from Haskell, a functional programming language, and Ruby, a “multi-paradigm” language (like JavaScript) that also encouraged functional programming patterns. Another influence was the Command Query Responsibility Segregation (CQRS) Pattern.6

Conceptually and ideologically, Flux paired very well with the programming model of React. In 2014, one year after React was announced, FB announced the Flux architecture.7 While Flux did ship with a small JS framework library, product engineers outside FB were encouraged to think about Flux as a design pattern that could also be implemented with custom frameworks. While the first public releases of React shipped without a strong opinion about an architecture for state management, FB was now evangelizing Flux as the correct “default” choice for most product engineers coming to the React Ecosystem.

ComponentKit

While front-end engineering for WWW was undergoing big changes at FB, mobile engineering for iOS was also evolving in new directions. From the early days of engineering at FB, the company was first and foremost a “web” company. The DNA of the company was very much tied into the WWW product and ecosystem. In an attempt to share engineering resources and knowledge, the FB “Big Blue” mobile app for iOS began to ship with many surfaces rendered in HTML5; it was a “hybrid app”. As the app began to grow (more products and more users), the performance limitations of HTML5 were impacting the reputation of the business: users were complaining. About the time WWW engineers were beginning to build React, FB engineers started to build a new version of the native mobile app for iOS.

As FB transitioned away from HTML5 hybrid views, engineers made some architectural decisions that would have important consequences later. FB mobile engineers chose MVC and UIKit as their main architecture. To manage their shared mutable state and data, mobile engineers chose the Core Data framework. The first native rewrites to the Big Blue FB app were successful: performance was much better than the hybrid app. This same year, FB publicly announced it was focusing on “mobile-first” growth.8 Historically, engineers at FB might launch new features on WWW. Launching that same feature on mobile iOS either meant writing HTML5 in a hybrid app, or waiting for one of the (few) native specialists at the company to build native Objective-C and UIKit.

With FB focusing on mobile-first growth, engineers from across the company that were shipping new products were now ramping up on learning UIKit and Core Data to ship on this new technology. Everything was good… until it wasn’t.

When the engineers building the native rewrite chose MVC, UIKit, and Core Data, the engineers were choosing what looked like the “best practices” at the time. These were the tools Apple built, and these were the tools Apple told engineers were the best for building applications at scale. While building an application using “conventional” iOS architecture and frameworks might have helped FB move fast and ship quickly, this architecture would soon lead to the same class of problems that caused the WWW team to pivot to React and Flux.

When placing and updating views on screen, the imperative and object-oriented programming model of UIKit and MVC was slowing engineers down. View Controllers were growing at quadratic complexity as the product scaled. Controllers — either one giant controller or a complex graph of controllers — would need to correctly position and mutate a graph of view objects using imperative logic. It was very easy for engineers to make a mistake that led to UI bugs and glitches. As this was happening, the Core Data framework was locking engineers into thinking about data as mutable model objects which were updated using imperative logic. Two-directional data bindings on these mutable model objects were leading to the same “cascading” class of bugs the front-end WWW team saw before Flux. On top of that, Core Data was really slow. Engineers tried all the tricks they could think of to speed up Core Data, but it led to unnecessary complexity that product engineers would have to work through and understand.

Neither of these frameworks (UIKit or Core Data) were scaling to support the ambitious goals of FB continuing to ship products with mobile-first growth. After about two years of struggling with MVC, a team of engineers led by Adam Ernst began an ambitious attempt to “rewrite” the app that had already been rewritten only two years before. They saw that the front-end WWW team encountered problems scaling products built on a MVC architecture, and the native mobile team was encountering the same class of problems. They saw that migrating to React and Flux solved these problems for WWW engineers, and they began to write an Objective-C++ native version of the React and Flux frameworks. These new frameworks, like React and Flux, would encourage declarative thinking instead of imperative thinking, functional programming instead of object-oriented programming, and immutable model values instead of mutable model objects.

The new UI framework was called ComponentKit.9 ComponentKit originally launched as a rewrite of the News Feed product, but ComponentKit spread to become the dominant framework product engineers would use for mobile iOS at FB. While ComponentKit was using the principles of React to solve the scalability problems of UI layout by migrating away from UIKit, a project called MemModels was in development to use the principles of Flux to solve the scalability problems of mutable state management by migrating away from Core Data. ComponentKit was released to the open-source community in 2015, but the “native” Flux framework was unfortunately not released publicly. Similar to the first version of React, FB presented a solution for bringing declarative programming to UI product engineering, but did not ship a companion framework for bringing declarative programming to complex state management.

Redux

The Flux framework and architecture was released to the open-source community in 2014. Over the following year, the engineering team behind Flux saw that product engineers at FB were beginning to repeat some of the same logic across products. Flux did not make assumptions about caching, faulting, paging, sorting, or other typical work that a network-driven application like FB might perform to fetch and present data from a remote server. For smaller companies and teams, the Flux framework might have been a great starting point for building the data model for smaller applications. For a rapidly growing company like FB, there was a lot of engineering impact that was being lost on duplicating logic across multiple product surfaces. The Flux team began to build a new framework which started with the philosophical foundation of Flux and offered new infra to help product teams working across FB that were blocked on these common problems.

The new framework was called [Relay]10, and was released along with the [GraphQL]11 data query language as a Flux-Inspired solution for managing the complex state of a network-driven application driven by a complex (but well defined) graph schema of data. The Relay framework was powerful and a huge leap forward over the initial release of Flux, but Relay was not a lightweight and general-purpose solution for state management: it was dependent on GraphQL as a schema to model data.

As FB engineers were building Relay, the React ecosystem and community continued to experiment with the Flux architecture. Over time, a number of legit grievances about decisions or ambiguity in the original Flux implementation led to the community beginning to think about what a next-generation evolution of Flux might look like.12

In 2015, Dan Abramov introduced the Redux framework and architecture at React Europe.13 For the most part, Redux began with many of the same opinions and assumptions of Flux: data still flowed in one direction with product engineers passing actions using declarative logic instead of mutating state directly at the component level with imperative logic. At the model layer, Flux Stores — which could be multiple stores in one application — contained imperative logic for mapping actions to mutations on state. Unlike Flux, Redux builds from just one store and saves engineers from managing complexity to keep multiple stores synchronized. The imperative logic to map actions to mutations on state is written outside of Stores in pure functions called Reducers. Using inspiration from Elm (a language and architecture emphasizing immutability), Redux Reducers map the state of an application with an action to produce the next state of the application.14 Using inspiration from ImmutableJS, Redux requires these state objects to be immutable — unlike Flux, which gave engineers the option to model their state with mutable objects.14

The Relay framework was a very powerful solution for managing network-driven applications built from GraphQL data schemas at scale. This project was hugely impactful inside FB and for applications built from a similar tech stack, but it was a “heavyweight” solution relative to the simplicity and flexibility of the original Flux implementation. Redux refined the original Flux implementation with ideas that reduced boilerplate and simplified state transformations. Leveraging immutable data structures led to code that was more predictable and easier to reason about. Over time, Redux became the dominant choice for unidirectional data flow state management for React Applications.15

SwiftUI

While the WWW team at FB was building the React framework in JS and the iOS team at FB was building the ComponentKit framework in Objective-C++, engineers at Apple led by Chris Lattner were building the Swift Programming Language.16 Swift brought some influences from C++ along with some influences from Objective-C. Swift also brought some influences from the functional programming patterns found in languages and ecosystems like Haskell and Ruby — which were also big influences on Flux.

One of the biggest differences between Swift and Objective-C was the flexibility and power of immutable value types. While simple C-style structs allocated on the stack were always available in Objective-C, the primary building blocks of almost all Objective-C applications were objects allocated on the heap. For an application like FB that was migrating away from the semantics of mutability, this led to workarounds like choosing Objective-C++ to improve the efficiency of creating objects and the Remodel library for adding “immutable” semantics to mutable data objects.[^17]17 Swift offered more flexibility for engineers by shipping powerful immutable value type structures along with support for mutable reference type classes. Swift Structs were far more flexible and powerful than Objective-C structs. Because Swift Structs followed value semantics, engineers were now able to “reason locally” about their code in a way that was not possible with reference semantics.18 Apple soon began to recommend structs and value semantics as the “default” choice for engineers to model their data types.19

Throughout the Objective-C era, Apple continued to evangelize MVC as the preferred application architecture for applications built from AppKit and UIKit.[^21]20 While Swift introduced powerful new abilities to encourage functional programming with immutable model values, the primary tools for building applications on Apple platforms were still AppKit and UIKit — which meant that the application architecture recommended by Apple continued to be object-oriented MVC.[^23]21

As early as 2017, rumors began to leak that Apple was building a new framework for declarative UI.22 In 2019, Apple announced SwiftUI.23 For engineers experienced with building applications in the FB ecosystem using React and ComponentKit, the programming model used by SwiftUI looked very familiar. SwiftUI “views” — similar to what React and ComponentKit called “components” — were built declaratively using immutable data structures. Rather than product engineers telling their view hierarchy how it should be built using imperative logic, product engineers started telling the SwiftUI infra what should be built using declarative logic. Like React and ComponentKit, SwiftUI encouraged product engineers to focus on “the what not the how”.

Considering the experience front-end teams from WWW and iOS had trying to scale classic MVC architectures across complex applications and large teams, a first-party solution for declarative UI built on a language that included support for immutable data values and functional programming looked like a huge leap forward for engineering on Apple Platforms. While the launch of SwiftUI offered a framework for managing graphs of view components declaratively, SwiftUI, like the early versions of React and ComponentKit, shipped without strong public opinions about what architecture should look like for mutable state management.

The early demos of SwiftUI from Apple emphasize what React Engineers would think of as “component state”.24 While Apple was encouraging a unidirectional flow of data through one subgraph of view components, we did not yet hear very clear messaging from Apple about what architecture we would use for a unidirectional flow of data across multiple subgraphs of view components. Without a clear new direction from Apple, many engineers across the community — engineers that might not have the context of what had been happening in and around FB — began to architect SwiftUI applications by using declarative logic to put view components on screen, but falling back to imperative logic on shared mutable state to transform user input into the new state of their system.25

SwiftData

In 2023, Apple launched a “next-generation” update to their Core Data framework. This new version was called SwiftData.26 SwiftData reduced some of the legacy artifacts, complex setup, and repetitive boilerplate code that was needed for many engineers using Core Data in modern Swift applications. What SwiftData did not offer product engineers was a fundamentally different programming model from what was already being offered in Core Data. When using SwiftData with SwiftUI, product engineers were still using imperative logic to mutate shared object references. The “UI” side of the application was modern and declarative, but the “Data” side of the application was still classic and imperative.

ImmutableData

As we build the ImmutableData infra and deploy the architecture to sample applications in SwiftUI, we will see how we can bring our mental model of “the what not the how” to a complete application architecture. With our experience building SwiftUI applications, we already know how to “think declaratively” for building a graph of view components; all we do now is complete the pattern across our “full stack”: UI and Data. As we build our sample applications, we will use declarative programming and a unidirectional data flow to demonstrate the same philosophies and patterns that scaled to one billion users across applications at FB and across the React ecosystem.

Once our applications are built and we see for ourselves what this architecture looks like, we will benchmark and measure performance. We will see how this architecture built from immutable data structures instead of SwiftData will save memory and CPU. We will even see how the ImmutableData architecture can continue to leverage SwiftData for some of its specialized behaviors: offering the improved performance and programming model of ImmutableData as a “front end” along with the efficient persistent data storage of SwiftData as a “back end”.

Migrating to ImmutableData might seem like we are asking you to “throw away” knowledge and experience, but we see a different point-of-view. We are asking you to expand the mental model you have already learned and practiced for “thinking in SwiftUI”. Bring this mental model with you as we see how declarative, functional, and immutable programming across the stack of our applications leads to code that is easy to reason about, easy to make changes to, and runs faster with less memory than SwiftData.

Let’s get started!


  1. https://en.wikipedia.org/wiki/History_of_Facebook

  2. https://engineering.fb.com/2010/07/21/core-infra/scaling-facebook-to-500-million-users-and-beyond/

  3. https://techcrunch.com/2009/09/15/facebook-crosses-300-million-users-oh-yeah-and-their-cash-flow-just-went-positive/

  4. https://about.fb.com/news/2012/10/one-billion-people-on-facebook/

  5. https://www.youtube.com/watch?v=GW0rj4sNH2w

  6. https://martinfowler.com/bliki/CQRS.html

  7. https://www.youtube.com/watch?v=nYkdrAPrdcw

  8. https://www.reuters.com/article/net-us-facebook-roadshow/facebooks-zuckerberg-says-mobile-first-priority-idUSBRE84A18520120512/

  9. https://engineering.fb.com/2015/03/25/ios/introducing-componentkit-functional-and-declarative-ui-on-ios/

  10. https://engineering.fb.com/2015/09/14/core-infra/relay-declarative-data-for-react-applications/

  11. https://engineering.fb.com/2015/09/14/core-infra/graphql-a-data-query-language/

  12. https://medium.com/@dan_abramov/the-evolution-of-flux-frameworks-6c16ad26bb31

  13. https://www.youtube.com/watch?v=xsSnOQynTHs

  14. https://redux.js.org/understanding/history-and-design/prior-art ↩2

  15. https://facebookarchive.github.io/flux/

  16. https://en.wikipedia.org/wiki/Swift_(programming_language)#History

  17. https://engineering.fb.com/2016/04/13/ios/building-and-managing-ios-model-objects-with-remodel/

  18. https://www.swift.org/documentation/articles/value-and-reference-types.html

  19. https://developer.apple.com/documentation/swift/choosing-between-structures-and-classes#Choose-Structures-by-Default

  20. https://developer.apple.com/library/archive/referencelibrary/GettingStarted/RoadMapiOS-Legacy/chapters/StreamlineYourAppswithDesignPatterns/StreamlineYourApps/StreamlineYourApps.html

  21. https://developer.apple.com/documentation/uikit/about_app_development_with_uikit

  22. https://mjtsai.com/blog/2018/05/01/scuttlebutt-regarding-apples-cross-platform-ui-project/

  23. https://developer.apple.com/videos/play/wwdc2019/103

  24. https://developer.apple.com/videos/play/wwdc2019/226

  25. https://www.youtube.com/watch?v=4GjXq2Sr55Q

  26. https://developer.apple.com/videos/play/wwdc2023/10187