Ditching Core Data
We recently released Watchville version 1.2.2. On the surface, this build carries only minor bug fixes. But behind the scenes there is an all new persistence layer. I finally got rid of Core Data.
I have been using Core Data since iOS 3.0. With few exceptions, it has served entirely as a network cache. Take data from a network response, turn it into a set of objects, save those objects, and update the user interface. Rinse and repeat.
The stack is pretty simple. A set of nested contexts to keep object building off of the main thread, a couple of models that have init(dictionary: NSDictionary), and some find-or-create helpers can get you a long way. The problem is that the apparent simplicity comes with an incredible amount of baggage. Instability, buggy classes, and outdated APIs make building modern apps difficult.
As a community, we have tried to solve these problems by building wrappers around Core Data. These wrappers add their own issues and none of them hide the fact that Core Data was simply not meant to be used like this.
As we discussed what the future might hold for Watchville, it became clear that if I wanted to make such a change, now was the time to do it. So I decided to start fresh. New models. New patterns. A whole new way of doing things.
After some research and planning, I landed on a set of features that I wanted in a persistence framework. The list is pretty short:
- My objects must be plain objects. Requiring model objects to extend a certain base class is painful; especially when you cannot see the source for those classes. I want to know that I can create objects however I like, use them wherever I like, and build my own functionality around them without worrying about what sandbox they belong to.
- I should not have to worry about concurrency. Tools like closures, Grand Central Dispatch, and NSOperationQueue make it easy to write code that spans many threads. Unpacking objects on background threads that are later used to update interface elements is a common operation that should be simple and easy to reason about.
- I should not have to write migration code. Frameworks and libraries can and should be smarter than this. Let me describe how I want the database to look. You take care of the rest.
- I should be able to track and respond to atomic changes. Elements in my application should be able to reload themselves if a single object, or a collection of objects changes. Otherwise it may spend too much time updating things that may not need to be updated, which could have a negative impact on the user experience.
I decided to use YapDatabase. It came highly recommended and it offers support for all of my requirements. YapDatabase is, at its core, a key-value store build on top of SQLite. It has a simple API and is incredibly flexible.
By default, YapDatabase uses NSCoding to read and write objects. A model object can be as simple as this:
You will notice a few things about my model objects right away: they are completely immutable, they have no knowledge of the persistent store, and no magic superclass. These objects can be passed around the application (and across thread boundaries) without requiring knowledge of the database or how these objects are to be handled.
If you do not want to use NSCoder, or you would like to perform your own object marshaling, YapDatabase offers the ability to provide custom serializers.
Reading & Writing
All database operations in YapDatabase are handled through database connections. YapDatabaseConnection has functions for reading from, writing to, and responding to individual changes in the database. It does all of this in a thread safe manner. One less thing to worry about! So a simple read operation from anywhere in the application looks like this:
And a write:
This is one of my favorite YapDatabase features. The secondary indexes extension gives applications the ability to attach indexable data to individual model objects so you can perform SQL-style queries on your data.
The extension is easy to setup:
Now, any time I save an article, its indexable data is automatically added to the database. Fetching specific objects is easy too. Let’s say I want to fetch all of the posts that belong to a certain feed:
The YapDatabase team has done an amazing job documenting all of the great stuff that it can do. I encourage you to consult their wiki to read about topics like YapDatabaseModifiedNotification and database views.
Why didn’t I use Realm?
Before I continue, I would like to say that I have great respect for anyone trying to tackle this problem. Solving the potential storage needs of every mobile application is incredibly challenging.
So, I get this a lot. Simply put, Realm is too much like the Core Data that I sought to leave behind. Here are a few examples:
- Your model objects must extend a base class that requires knowledge of the persistent store. So everywhere you use one of those models you are bringing that baggage with you.
- There is currently no way to get detailed change notifications.
- Instances of RLMObject belong to a specific realm which can be accessed only from the thread on which it was created.
I am incredibly happy with how YapDatabase is performing in Watchville. Our crash volume is down and it is now easier to reason about the model code, which means I can focus more on building new stuff and less on the nuances of Core Data.
I hope all this is helpful if you are looking to make a similar move. Let me know if you have any questions or comments! I’m @calebd on Twitter.