Migrating an App from Core Data to Realm

This article is outdated and no longer representative of some of the features in Realm mentioned as missing, such as optional properties.

Porting an app using Core Data to Realm is very simple indeed. If you have an app that already uses Core Data, and have been considering the move to Realm, this step-by-step guide is for you!

Many of the developers featured on our Users page once had apps with deep Core Data integration (sometimes with thousands of LOC), and many will tell you that the conversion process took only a few hours. Core Data and Realm both handle your data as objects, so migration is usually very straight-forward: simply take your existing Core Data code and refactoring it to use the Realm API.

After migrating, you should be thrilled with the ease of use, speed, and stability that Realm can bring to your apps.

1. Remove the Core Data Framework

First things first, if your app is currently using Core Data, you’ll need to work out which parts of your codebase include Core Data code. These will need to be refactored. Fortunately, there’s a handy way to do this: While you could manually perform searches on the codebase looking for the relevant code, a much easier solution is to simply delete the Core Data import statements at the top of your source files:

#import <CoreData/CoreData.h>
//or
@import CoreData;

Once this is done, every line of code implementing Core Data will throw a compiler error, and then it’s simply a matter of addressing each compiler error, one at a time.

2. Remove Core Data Setup Code

In Core Data, changes to model objects are made against a managed object context object. Managed object context objects are created against a persistent store coordinator object, which themselves are created against a managed object model object (Whew! What a mouthful!).

Suffice to say, before you can even begin to think about writing or reading data with Core Data, you usually need to have an implementation somewhere in your app to set up these dependency objects and to expose Core Data’s functionality to your app’s own logic. Whether it’s within your application delegate object, a globally accessible singleton, or even just inline implemented in your code, there will be a sizable chunk of ‘setup’ Core Data code lurking somewhere.

Get more development news like this

When you’re switching to Realm, all of that code can go. (Kill it! Kill it with fire!)

In Realm, all of the setting up is done on your behalf when you access a Realm object for the first time, and while there are options to configure it — such as where to place your Realm data file on disk — it’s all completely optional at runtime.

RLMRealm *defaultRealm = [RLMRealm defaultRealm];

Feels good, doesn’t it?

3. Migrate Your Model Files

In Core Data, the bread-and-butter class that defines subclassed model objects to be persisted is NSManagedObject. The interfaces of these kinds of objects are pretty much standard, with the main caveat that primitive types (Such as NSInteger and CGFloat) cannot be used and must be abstracted through an NSNumber.

@interface Dog : NSManagedObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSNumber *age;
@property (nonatomic, strong) NSDate *birthdate;

@end

@implementation Dog

@dynamic name;
@dynamic age;
@dynamic birthdate;

@end

Converting these managed object subclasses to Realm is really simple:

@interface Dog : RLMObject

@property NSString *uuid;
@property NSString *name;
@property NSInteger age;
@property NSDate *birthdate;

@end

@implementation Dog

+ (NSString *)primaryKey {
  return @"uuid";
}

+ (NSDictionary *)defaultPropertyValues {
  return @{ @"uuid" : [[NSUUID UUID] UUIDString],
            @"name" : @"",
            @"birthdate" : [NSDate date] };
}

@end

Done! How easy was that?

Looking at the implementation, there are a few Realm niceties to note.

For starters, it’s not necessary to specify any property keywords — Realm manages these internally — so the class header looks much more minimal. Additionally, Realm supports simple numerical data types such as NSInteger and CGFloat, so all of that NSNumber cruft can safely (and gladly!) be removed.

On the flip side, there are a few additional caveats in the Realm model implementation that deserve highlighting:

  • While Core Data objects have an internal NSManagedObjectID property to uniquely identify objects, Realm leaves this decision up to you as the developer. In the above example, we’ve added an additional property named uuid and then used the [RLMObject primaryKey] method to mark it as the unique identifier for this class. However, if your objects don’t need to be uniquely identified at all, all of this can be skipped.

  • At the time of this writing (but not for much longer!), Realm cannot handle object properties that are nil. As such, the [RLMObject defaultPropertyValues] class method here defines a set of default values for each object property that will be set when the object is initially created. While this is a slight inconvenience for the time being, we’re glad to say that the ability to set Realm object properties as nil will be added in an upcoming update.

4. Migrate Your Write Operations

It wouldn’t be much of a persistence solution if you couldn’t save your data! Creating a new object in Core Data and then later modifying it is relatively trivial, only taking a few lines of code:

//Create a new Dog
Dog *newDog = [NSEntityDescription insertNewObjectForEntityForName:@"Dog" inManagedObjectContext:myContext]; 
newDog.name = @"McGruff";

//Save the new Dog object to disk
NSError *saveError = nil;
[newDog.managedObjectContext save:&saveError]; 

//Rename the Dog
newDog.name = @"Pluto";
[newDog.managedObjectContext save:&saveError]; 

In contrast, Realm save operations are handled slightly differently, though still similar enough where modifying the above code in the same scope is possible.

//Create the dog object
Dog *newDog = [[Dog alloc] init];
newDog.name = @"McGruff";

//Save the new Dog object to disk (Using a block for the transaction)
RLMRealm *defaultRealm = [RLMRealm defaultRealm];
[defaultRealm transactionWithBlock:^{
  [defaultRealm addObject:newDog];
}];

//Rename the dog (Using open/close methods for the transaction)
[defaultRealm beginWriteTransaction];
newDog.name = @"Pluto";
[defaultRealm commitWriteTransaction];

Boom! Done! Our data is persisted!

What’s immediately obvious with Realm is that objects are non-modifiable once they’ve been added to a Realm object. After that, the Realm object in which they were saved needs to be in a ‘write’ transaction in order to modify these properties afterwards. This immutable model ensures data consistency when reading/writing object data in different threads.

As Core Data’s implementation is literally just a matter of changing the properties and then calling the ‘save:’ method, porting that code to Realm’s implementation, while slightly different is still quite trivial.

5. Migrate Your Queries

It wouldn’t be much of a persistence solution if, on the flip side, you couldn’t retrieve your data either!

In it’s most basic implementation, Core Data uses the concept of fetch requests in order to retrieve data from disk. A fetch request object is created, and then any additional filtering parameters and sorting options are inserted as separately instantiated objects (Whew!).

NSManagedObjectContext *context = self.managedObjectContext;

//A fetch request to get all dogs younger than 5 years old, in alphabetical order
NSEntityDescription *entity = [NSEntityDescription
entityForName:@"Dog" inManagedObjectContext:context];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age < 5"];

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];

NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = entity;
request.predicate = predicate;
request.sortDescriptors = @[sortDescriptor];

NSError *error;
NSArray *dogs = [moc executeFetchRequest:request error:&error];

While that certainly works well enough, it is a lot of code to write! It’s so much code that a group of really smart developers worked on developing a library to make it easier.

Compared to all of that, here’s the equivalent code of such a query, using Realm:

RLMResults *dogs = [[Dog objectsWhere:@"age < 5"] sortedResultsUsingProperty:@"name" ascending:YES];

Two method calls, one line of code. One. As opposed to the approximately ten lines of code in Core Data. Outrageous.

Again, with the resulting output of both operations being the same (RLMResults behaves exactly like an NSArray), converting this functionality to Realm is very self-contained and requires very little refactoring of the surrounding logic to implement.

6. Migrate Your Users’ Production Data

Once all of your code has been migrated to Realm, there’s one more outstanding issue, and it’s a doozy: How do you migrate any production data that users may already have on their devices out of Core Data and into Realm?

Obviously, this can be a very complex issue, and depending on your app’s function, as well as your users’ circumstances, how you should go about handling this can end up being very different each time.

At present, we’ve seen two major cases:

  1. Once you’ve migrated to Realm, you can re-link the Core Data framework back into your app, use raw NSManagedObject objects to fetch your users’ data from Core Data, and then manually pass it over to Realm. You can leave this migration code in your app permanently, or simply remove it after a sufficient period of time has passed.

  2. If the user’s data isn’t irreplaceable — for example, if it is simply cached information that could be regenerated by other user data on disk — then it may be easier to simply blow all of the Core Data save files away, and start from scratch when the user next opens the app. Obviously this needs to be done with very careful consideration, or else it could end up being a bad user experience for a lot of people.

Ultimately, the decision here is up to you and what would be best for your users. Ideally, not needing to leave Core Data linked to your app would be best, but that outcome will definitely depend on your situation. Good luck!

Advanced Topics

While not really essential steps for porting an app to Realm, there are some additional circumstances in which you should be aware:

Concurrency

More often than not, if you’re doing some heavy processing in a background queue, you may find yourself needing to pass a Realm object between threads. While Core Data DOES allow you to pass managed objects between threads (though it’s not best practice), in Realm’s case, passing objects between threads is explicitly disallowed, and any attempts to do so will result in an utterly catastrophic exception being thrown.

That being said, there’s a very easy work-around for these situations:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
  //Rename the dog in a background queue
  [[RLMRealm defaultRealm] transactionWithBlock:^{
    dog.name = @"Peanut Wigglebutt";
  }];

  //Print the dog's name on the main queue
  NSString *uuid = dog.uuid;
  dispatch_async(dispatch_get_main_queue(), ^{
    Dog *localDog = [Dog objectForPrimaryKey:uuid];
    NSLog(@"Dog's name is %@", localDog.name);
  });
});

While Realm objects cannot be passed between threads, copies of Realm properties can be passed between threads. And given how fast Realm can retrieve objects from disk, simply refetching the same object from the store on the new thread is usually a very trivial performance hit. In this instance, we took a copy of the object’s primary key, passed it from the background queue to the main queue and then used it to re-fetch the specific object we were working with in the context of the main queue.

NSFetchedResultsController Equivalent

Given all of its foibles, perhaps the strongest reason to use Core Data is the NSFetchedResultsController — a class that can observe when the data it stores is changed, and automatically forward these changes to the UI.

At the time of writing, Realm doesn’t have a similar mechanism. It has the ability to register a block that will be executed whenever a change is registered in the data store, but this “brute force” approach isn’t great for the majority of UI cases. At present, this could be a deal-breaker for you, if your UI code depends heavily on this.

That being said, the Cocoa engineers at Realm are finalizing the implementation of a fine-grain notification system, where it’s possible to register for a notification when a specific object property is modified. Look out for this feature in a future Realm Swift and Realm Objective-C update.

In the meantime, if the present notification block API doesn’t suit your needs, but you’d still like to be notified when a specific property is changed, there’s a fantastic third-party library named RBQFetchedResultsController, which is able to emulate this functionality. Beyond that, you can also replicate this functionality locally in your own code by adding convenience setter methods in your objects that broadcast NSNotification events whenever the setter is called.

Conclusion

Thanks to their similarities in exposing data through model objects, converting an app from using Core Data to Realm is very quick and simple (and usually pretty satisfying too!). While it may seem daunting at first, it’s often a very simple matter of taking each Core Data method call, converting it into its Realm equivalent, and then writing a helper class to migrate your users’ data across.

If you’ve been having trouble getting Core Data working in your app and would like something a bit easier, we strongly recommend giving Realm a try, to see if it works for you. And if it does, please be sure to let us know!


Thanks for reading. Now go forth and build amazing apps with Realm! As always, we’re around on StackOverflow, GitHub, or Twitter.


Tim Oliver

Tim Oliver hails from Perth, Australia! He has been an iOS developer for 6 years, and recently joined Realm in March 2015. Tim has a cool app called iComics and he loves karaoke! He does, in fact, also sometimes have the problem of too many kangaroos in his backyard.