How Immutable State Helped Facebook to Improve Its iOS App Architecture
Facebook has been working in the last two years to evolve the architecture of its iOS app with the goal of improving performance, abstractions, and the underlying development model. Adam Ernst and Arl Grant, software engineers at Facebook, explained what issues they had to solve and how they did in a @Scale 2014 talk.
When Facebook engineers realised that adding new features to their app caused it to become slower, they quickly found that the issue lied with the model layer and was related to the use of Core Data. Specifically, they found problems on three fronts, Adam said:
setup performance: the time required to set up a Core Data graph grows not only with the number of entities but also with the number of relationships and became quickly unbearable.
runtime performance: since Core Data uses a global persistent store, any time a component attempts to save some data, this is going to lock access to the store for others, causing short UI freezes. Adam also mentions Core Data faulting mechanism as an hindrance, since resolving a fault happens at the time the data is required by the UI and this contributed to scrolling lag.
development model: in Core Data models are mutable, so anyone can change anything, at any time. This makes it eventually difficult to track who is doing what when bugs arise. Furthermore, Core Data makes multi-threading more complex than it would be desirable, due to the unsafety of passing Core Data object between threads. Finally, Core Data encourages two-way bindings, which is a nice idea but will produce a lot of cascading UI updates in larger apps, making it also difficult to reason about cycles and race conditions.
Given all of this, Facebook engineers decided to build their own caching layer. Their requirements were the following:
- no read-write properties;
- no way for developers to immediately modify the model;
- separate stores for different app subsystems, so to avoid blocking and also allowing each subsystem to use the most appropriate store to it;
- consistency management across stores.
Immutability implies that for any change to an object, a new hierarchy of objects is created with the new values. To improve performance, if a part of that hierarchy does not change, it will get reused.
The consistency manager listens for changes in the model and notifies registered parties about them to provide eventual consistency among the separate stores.
Moving to this new persistence layer brought a 35% performance improvement and better stability when adding new features, Adam said.
The same kind of architecture rethinking went on for the UI layer, Arl explained.
Facebook starting point was the standard Model-View-Controller architecture that Apple suggests. Facebook engineers found that the MVC architecture quickly brings to having a large number of controllers, each concerned with some specific portion of the UI, interacting with the rest of controllers. The number of such dependencies made it very hard to understand what was going on when things did not work correctly.
Another limitation Facebook engineers found was related to handling mutable state across multiple threads. The simplest approach, which implies dealing with any UI changes on the main thread (due to UIKit being not thread-safe) turned out quickly to produce sluggish performance. The alternative, which entails the use of locks, is complex and at risk of deadlocks.
So, Facebook engineers set off for a complete rewrite of their UI code and aimed at creating an infrastructure responsible for:
- configuring and calculating the layout of the views à la Flexbox;
- listen for model state changes;
- handle multithreading issue and make asynchronous computation (like text layout or image decoding) transparent.
Many of the concepts used were ported from React to Objective-C++, Arl said. The general idea is the following:
- the developer provides a pure function, the component, that takes some data and returns an immutable description of the view as a tree of objects.
- when a new event comes in, the infrastructure renders a new view by executing the pure function.
One big advantage of this is that it only entails a one-way data flow, from the model to the infrastrcture through the component definition, instead of a more complex graph involving several communicating controllers. Actually, Facebook engineers refactored their existing design by simply converting each controller into a component and breaking the dependency between controllers and views, which are handled through the infrastructure.
A key point to make all of this infrastructure efficient is view recycling. So each time a new view has to be generated, the infrastructure looks for a suitable view to be reused; if possible, subviews are also reused. The complexity of this is O(n) with the size of the view hierarchy tree.
Thanks to the new infrastructure, Facebook could reduce the code base size by 70%. Furthermore, immutability and one-way flow of events has made, according to Arl, much easier to reason about what is happening.
The language Facebook chosen for it is Objective C++ because of types, const safety typed collections, nil-safe collections, efficiency, and aggregate initialization.
You can also listen to Adam Ernst interviewed for InfoQ on functional approaches to iOS programming.