Migrating to Realm on iOS

Remind migrated to Realm from Core Data at the end of 2015, with Alex leading the migration. Getting up and running with Realm is a cinch, but what about when you have an existing app already loose in the wild that’s entrenched in Core Data? This can mean maintaining two databases simultaneously before flipping the switch to Realm, which introduces a few extra complications. Alex discusses the reasons Remind chose to migrate and his experience leading the migration, as well as a few of the things he would have done differently in hindsight so you can learn from his mistakes and make that migration to Realm you’ve been contemplating nice and smooth.


Introduction (0:00)

Hi, I’m Alex. I’m an iOS engineer at Remind. We know about Realm’s advantages, and we’re already using it in the prototyping of other applications, but some of us are already working in applications that are really deeply entrenched in Core Data and its patterns and tooling. You may want to switch, but you face the prospect of a potentially long migration and a risky release, which may deter you.

This talk is meant to give you a path to a gradual migration where you can drop Core Data and feel safe about doing it.

Taking Both Pills (2:42)

I’m here to tell you that you can probably take both pills. This is what we did at Remind. We were able to roll out a Realm implementation alongside our Core Data stack in order to test out new UI patterns, new object mapping code, and a better threading model, and all this is behind a server side kill switch in case we got anything wrong.

Remind’s Starting Point (3:06)

Remind’s iOS application was originally built on top of raw, do-it-yourself Core Data - this was before I got there. When I arrived at Remind, one of my first projects was to integrate a library called RestKit. We were also using a tool called Mogenerator to generate all of our NSManagedObject subclasses, and that reads your data model file and generates all the property accessors and mutators and relationships. So when your data model changes, none of your object classes have to be regenerated.

Get more development news like this

By far the biggest risk of that setup was in the object mapping layer. Data going into a database is obviously very strict about what data types that you put in; whereas JSON coming down in a payload is kinda wishy-washy and it doesn’t always contain what you expect it to contain. Sometimes you want a number and it actually comes out as a string. Sometimes you expect an integer, but somewhere along your service pipeline, something coerces it into a double and that completely bones your database write.

On a certain level these are bugs in your API code and they can theoretically be fixed, but at the same time, old bugs exist, new bugs are going to happen, and as client engineers it’s our job to make sure that our client doesn’t explode if we see something we don’t expect. RestKit did a lot of its defensive mapping for us and so this was largely unknown territory for us, and we were super nervous about what kind of cases we might run into that were going to cause our application to crash in the wild, and what kind of stuff we had to protect against on our end to make sure that everything was stable. Like I said before, this isn’t really a hard problem, but it can have a lot of unknowns, and failures in this layer are going to affect every user in the wild.

Server-Side Kill-Switch + Monitoring (6:15)

This brings me to the next important point: you definitely need to have a remote panic button. If you get something wrong, you need a way to turn off all the malignant code paths from your server. In our case we had a set of booleans that came down in our user request and that allows us to toggle any code in our application on and off. That also means you need a way to tell that things are going wrong in the wild. That’s where something like an event tracking system that you can ping when something unexpected happens in your Realm code makes sense. You also need a crash reporter that you can monitor after your release. The whole point of doing this migration gradually is so that you can test the waters and enter very safely, so if you don’t have a server side kill switch you might as well do the whole migration at once because it’s just a crapshoot after that.

Parallel Databases (7:06)

Parallel databases means that we need parallel data models in our project. In Core Data you’ll have a class called User, in Realm you’ll have a class called Realm User. This feels really bad, because it is the exact opposite of the don’t repeat yourself principle. Unfortunately, if you want to roll this out gradually, this is kind of a necessary evil you’ll have to come to terms with. It’s easy enough to copy your properties into a new Realm subclass, but the bigger issue is going to be maintaining parallel custom logic in those models, and dealing with the code that actually talks to those models. So the solution to that will depend on how long you intend to maintain both databases.

I strongly recommend that you iterate for a short while to gain confidence in your migration and then make a clean switch to Realm. If you go this route you’ll only have to suffer for a short time while maintaining both of your database models together, and database models in general should be fairly stable classes in terms of additional logic and will hopefully be a bigger pain in your soul than it is in your actual workflow.

Integrating Initial UI Updates (10:39)

At this stage we want to pick some kind of screener application that is relatively stable but also relatively low traffic, and represents the standard set of UI interactions that you’re going to be using throughout your application. If your application has a lot of lists, you want to make sure that you can load a table view from your Realm database. If you do a lot of dynamic querying through your app, through something like a search interface, you want to make sure that your existing queries are going to translate well to what Realm can support at this time, or figure out how you’re going to tweak those to fit into this new model.

We started with a collection view displaying stuff from the Realm database, hitting the network layer, getting the response, and mapping it, so it was pretty simple representation of what you do in the rest of the application. At this point, you’re going to run into yet another parallelism problem, because the rest of your application is dealing with Core Data models, and now you want to introduce an interface that deals with Realm models. You’ve got two choices there again:

1) You can duplicate your View Controller for different interfaces, 2) You can contain that conditional switching within the same View Controller.

Neither of those are really great options, but if you’re just doing this as a trial period to cut it off, it’s manageable to keep this parallel stuff in check, and at the end of it you’ll be able to run multiple experiments on multiple parts of your UI to build up your confidence before you take the full plunge into Realm.

Interoperability (12:49)


@interface RLMObject (CoreDataMatching)
+ (NSString*)coreDataEntityName;
- (NSPredicate*)coreDataMatchingPredicate;
- (id)coreDataEntity: (NSManagedObjectContext*)context;
@end

@interface NSManagedObject (RealmMatching)
+ (NSString*)realmEntityName;
- (NSPredicate*)realmMatchingPredicate;
- (id)realmEntity:(RLMRealm*)realm;
@end

@implementation RLMObject (CoreDataMatching)
- (NSPredicate*)coreDataMatchingPredicate {
  NSString* primaryKey = [[self class] primaryKey];
  id primaryValue = [self valueForKeyPath:primaryKey];
  return [NSPredicate predicateWithFormat:@"%K == %@", primaryKey, primaryValue];
}

@implementation NSManagedObject (RealmMatching)
- (__kindof RLMObject)realmEntity:(RLMRealm*)realm {
  NSString *entityName = [[self class] realmEntityName];
  Class entityClass = NSClassFromString(entityName);

  NSPredicate *predicate = [self realmMatchingPredicate];
  RLMResults *results = [entityClass objectsInRealm:realm withPredicate:predicate];
  return [results firstObject];
}

So once you get a screener to retool to work with Realm models to start using them, the Core Data parts of your application need to be able to supply the Realm parts of your application with Realm models. Just think of it as if you had a Master View Controller that’s playing a list of Core Data models and you’ve rewritten your Detail View Controller that’s displaying one of those models. When you tap a Core Data model, you need to fetch the corresponding Realm entity to give it the Detailed View Controller. You need to setup a naming scheme.

User becomes Realm User, Group becomes Realm Group - all of our entities are uniquely identifiable by some server assigned ID, so it’s easy to set up a predicate to find a corresponding object in the other database

Above is the code for generating the predicate to find a Core Data model matching the current Realm model.

Finishing the Job (15:25)

So you get through your trial period and you decide to go forward with Realm, because Realm is awesome. When that happens though the real work of the migration begins, and there are a couple decisions that can affect the duration of your migration by orders of magnitude.

This slide is Zach. Zach is my product manager on my team at Remind. Zach loves shipping features, but Zach is not an engineer. I made two large mistakes in starting our final migration process that made Zach sad.

The first one was dramatically underestimating how much work was involved in completing our migration. These were a few of the challenges I underestimated:

  • Realm doesn’t come with a quick replacement for a few Core Data features that are pretty common for a lot of applications - things like nested keypath sorting isn’t supported yet.

  • Cascading object deletions isn’t built into their life cycle like it is in Core Data.

  • Unstructured data like dictionaries that you would normally store in a transformable property - you’re going to have to structure those somehow or otherwise serialize them to store them into Realm, and again these aren’t complex problems that are unsolvable.

The second big mistake was doubling the time it took to do the migration. I followed the same approach that I did with the screens during the transition minus putting them behind a kill switch. I’d update one interface which would break all the references to it. Essentially changing a light bulb would turn into repairing a car because at every step along the way something else was broken and had to be fixed. Changing one interface just produces a trail of tears and broken references that had to be cleaned up, and by the time you’re done you’re not even sure where you started anymore. Eventually you do reach a point where you can actually start ripping out all of your Core Data models, cleaning up any dangling references, and just cleansing your code of all things Core Data.

Recap (22:57)

Just to summarize, even if you’re really deeply entrenched in the Core Data patterns and tooling in your application, you can still roll out a migration to Realm gradually, safely, and with high confidence if you invest in a try-before-you-buy scenario like this.

Test out your UI patterns in Realm, make sure everything is behind your kill switch, monitor your results after each release, and when you are ready to drop Core Data, be smarter about it than I was and don’t forget about Zach. Do your best to estimate how much work your code base will be to migrate, but at the end of the day, it’s a very fundamental changing of your architecture, so bank on some unforeseen obstacles and keep everybody in the loop.

Q&A (24:00)

Q: Was the migration worth it?

The migration was super worth it if only for how much better the interfaces are for Realm than for Core Data. A lot of our instability in our current app stems from having RestKit dealing with Core Data, and the thing about Core Data is when it fails, it fails unpredictably for us. We have a consistent baseline crash rate just from Core Data crashes that we can’t figure out because there’s just no way to decipher what we did wrong. That was basically the original motivation for it. We were looking for a win in terms of our crash rate, in terms of our stability, and we thought the single biggest thing we could do to help our application was to move to Realm.

Q: How far along the process are you now? Is it complete?

We are super excited and ready to make the switch, and at this point we’ve just got a couple things in our application that really need fine grain notifications that we get from Core Data and that we’re so close to with Realm. We’re really just in a holding pattern until that’s available and then we are pretty much ready to ship.

Alex Leffelman

Alex is a Software Engineer at Remind. When he joined in November 2013, he doubled the size of the iOS team. His previous work includes two years developing Zynga Poker’s iOS app, as well as a year designing note tracks for several Guitar Hero titles at Neversoft Entertainment. A proud native Wisconsinite, Alex enjoys all things beer, cheese, and Badgers.