Keeping Immutable Models Consistent
April 27, 2016
When we rewrote the LinkedIn flagship app last year, we decided to use immutable models everywhere because of the many benefits that they provide, such as thread safety and better performance. Instead of using raw JSON dictionaries for data, we parse our data into objects which the application uses for data access. The main challenges of using immutable models are handling mutations and maintaining consistency. To solve these problems, we created the iOS Consistency Manager.
The iOS Consistency Manager is a Swift library that helps you maintain model consistency by allowing objects to listen to changes on an immutable model. Whenever an updated version of this model is created, the consistency manager will regenerate a new model for each listener which has changed. With the iOS Consistency Manager, you can use thread-safe, performant, immutable models while still being able to manage model changes.
1. Thread safety
For both iOS and Android applications, there is a single UI thread. All UI operations must be done on this thread, and if it is blocked, the app will freeze and start dropping frames. So, the majority of our infrastructure code heavily uses background threads for most tasks. With immutable models, this is always safe, and you don't need to re-request models from a store.
Our immutable models are always in memory and always immediately available. This makes access really fast. Other modeling systems, like CoreData, lazily load models from disk into memory. When accessing a property, CoreData may need to load a model from disk causing lookup time to be variable. If the object is missing, CoreData will crash. We never need to worry about faulting models into memory or worry about crashes when a fault fails.
3. Easier Debugging
Even when you are using mutable models correctly, when you find a bug in the program, you need to verify certain assumptions. Let's say there is a bug where a label sometimes disappears. One theory might be that a field is being set to null. With immutable models, you don't even need to investigate this theory and can concentrate your efforts elsewhere. Immutable models help isolate complexity.
4. Functional Code
Since models don’t have side effects anymore, functions tend to become much more functional. This again makes debugging easier because it’s easier to find where changes take place.
Keeping Models Consistent
All of our models can be represented as trees. We have a root object which contains references to other objects or arrays of other objects. Each node can also have an ID, which lets the consistency manager identify shared resources across different pages.
Let’s take the example of a messaging app with two models which, in tree form, look like this:
The application has two view controllers, each containing a reference to one of these models.
Later in the application, some source, like a network request, push notification, or user action, indicates that a person with id = 12 has come online. The application posts a new model to the consistency manager:
The consistency manager finds that two models, Message and Contacts, need updating and creates new copies of these models with the updated Person model:
The consistency manager then delivers the updated models to the subscribed listeners (view controllers in this case) via delegate callbacks. The view controller simply needs to set the new data and refresh its view.
This way of updating views is much simpler than a system like key-value observing since the data always flows in one direction. A model will never tell a view controller to update. Instead, updates always come in one direction, which means that the view controller only needs to write its layout code in one way.
The data that comes from the consistency manager is the same type as the data that comes from the network, so the code which renders the view can be reused. The view controller (or another object in the application) notifies the consistency manager of a change, and the consistency manager sends this out to all view controllers that care about this change.
The consistency manager also has support for deleting models, batch updating, batch listening, and pausing listeners.
The consistency manager has helped us write a data model manager that uses immutable models. This allows us to write much more stable and performant code than we could manage with other options like CoreData. We’ve easily managed to scale this to hundreds of models and view controllers in our app.
We think that the consistency manager would be useful in other applications, so we’ve open-sourced the consistency manager. You can check out the code on GitHub and check out the documentation here.