HomeLearnHow-toMigrating a SwiftUI iOS App from Core Data to Realm

Migrating a SwiftUI iOS App from Core Data to Realm

Published: May 21, 2021

  • Realm
  • Mobile
  • Swift
  • ...

By Andrew Morgan

Rate this article

Porting an app that's using Core Data to Realm is very simple. 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! The way that your code interacts with Core Data and Realm is very different depending on whether your app is based on SwiftUI or UIKit—this guide assumes SwiftUI (a UIKit version will come soon.)

You're far from the first developer to port your app from Core Data to Realm, and we've been told many times that it can be done in a matter of hours. Both databases handle your data as objects, so migration is usually very straightforward: Simply take your existing Core Data code and refactor it to use the Realm SDK.

After migrating, you should be thrilled with the ease of use, speed, and stability that Realm can bring to your apps. Add in MongoDB Realm Sync and you can share the same data between iOS, Android, desktop, and web apps.

This article was updated in July 2021 to replace objc and dynamic with the @Persisted annotation that was introduced in Realm-Cocoa 10.10.0.

#Prerequisites

This guide assumes that your app is written in Swift and built on SwiftUI rather than UIKit.

#Steps to Migrate Your Code

#1. Add the Realm Swift SDK to Your Project

To use Realm, you need to include Realm's Swift SDK (Realm-Cocoa) in your Xcode project. The simplest method is to use the Swift Package Manager.

In Xcode, select "File/Swift Packages/Add Package Dependency...". The package URL is https://github.com/realm/realm-cocoa:

Add the Realm Swift SDK to the Xcode project using the Swift Package Manager feature in Xcode.

You can keep the default options and then select both the "Realm" and "RealmSwift" packages.

#2a. The Brutalist Approach—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:

1import 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.

#2b. The Incremental Approach—Leave the Core Data Framework Until Port Complete

Not everyone (including me) likes the idea of not being able to build a project until every part of the port has been completed. If that's you, I'd suggest this approach:

  • Leave the old code there for now.
  • Add a new model, adding Realm to the end of each class.
  • Work through your views to move them over to your new model.
  • Check and fix build breaks.
  • Remove the Realm from your model names using the Xcode refactoring feature.
  • Check and fix build breaks.
  • Find any files that still import CoreData and either remove that line or the entire file if it's now obsolete.
  • Check and fix build breaks.
  • Migrate existing user data from Core Data to Realm if needed.
  • Remove the original model code.

#3. 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.

Suffice to say, before you can even begin to think about writing or reading data with Core Data, you usually need to have code somewhere in your app to set up these dependency objects and to expose Core Data's functionality to your app's own logic. There will be a sizable chunk of "setup" Core Data code lurking somewhere.

When you're switching to Realm, all of that code can go.

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.

#4. Migrate Your Model Files

Your Realm schema will be defined in code by defining your Realm Object classes. There is no need for .xcdatamodel files when working with Realm and so you can remove those Core Data files from your project.

In Core Data, the bread-and-butter class that causes subclassed model objects to be persisted is NSManagedObject. The classes for these kinds of objects are pretty much standard:

1import CoreData
2
3@objc(ReminderList)
4public class ReminderList: NSManagedObject {
5 @NSManaged public var title: String
6 @NSManaged public var reminders: Array<Reminder>
7}
8
9@objc(Reminder)
10public class Reminder: NSManagedObject {
11 @NSManaged var title: String
12 @NSManaged var isCompleted: Bool
13 @NSManaged var notes: String?
14 @NSManaged var dueDate: Date?
15 @NSManaged var priority: Int16
16 @NSManaged var list: ReminderList
17}

Converting these managed object subclasses to Realm is really simple:

1import RealmSwift
2
3class ReminderList: Object, ObjectKeyIdentifiable {
4 @Persisted var title: String
5 @Persisted var reminders: List<Reminder>
6}
7
8class Reminder: EmbeddedObject, ObjectKeyIdentifiable {
9 @Persisted var title: String
10 @Persisted var isCompleted: Bool
11 @Persisted var notes: String?
12 @Persisted var dueDate: Date?
13 @Persisted var priority: Int16
14}

Note that top-level objects inherit from Object, but objects that only exist within higher-level objects inherit from EmbeddedObject.

#5. Migrate Your Write Operations

Creating a new object in Core Data and then later modifying it is relatively trivial, only taking a few lines of code.

Adding an object to Core Data must be done using a NSManagedObjectContext. This context is available inside a SwiftUI view through the environment:

1@Environment(\.managedObjectContext) var viewContext: NSManagedObjectContext

That context can then be used to save the object to Core Data:

1let reminder = Reminder(context: viewContext)
2reminder.title = title
3reminder.notes = notes
4reminder.dueDate = date
5reminder.priority = priority
6
7do {
8 try viewContext.save()
9} catch {
10 let nserror = error as NSError
11 fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
12}

Realm requires that writes are made within a transaction, but the Realm Swift SDK hides most of that complexity when you develop with SwiftUI. The current Realm is made available through the SwiftUI environment and the view can access objects in it using the @ObserveredResults property wrapper:

1@ObservedResults(Reminder.self) var reminders

A new object can then be stored in the Realm:

1let reminder = Reminder()
2reminder.title = title
3reminder.notes = notes
4reminder.dueDate = date
5reminder.priority = priority
6$reminders.append(reminder)

The Realm Swift SDK also hides the transactional complexity behind making updates to objects already stored in Realm. The @ObservedRealmObject property wrapper is used in the same way as @ObservedObject—but for Realm managed objects:

1@ObservedRealmObject var reminder: Reminder
2TextField("Notes", text: $reminder.notes)

To benefit from the transparent transaction functionality, make sure that you use the @ObservedRealmObject property wrapper as you pass Realm objects down the view hierarchy.

If you find that you need to directly update an attribute within a Realm object within a view, then you can use this syntax to avoid having to explicitly work with Realm transactions (where reminder is an @ObservedRealmObject):

1$reminder.isCompleted.wrappedValue.toggle()

#6. Migrate Your Queries

In its most basic implementation, Core Data uses the concept of fetch requests in order to retrieve data from disk. A fetch can filter and sort the objects:

1var reminders = FetchRequest(
2 entity: Reminder.entity(),
3 sortDescriptors: NSSortDescriptor(key: "title", ascending: true),
4 predicate: NSPredicate(format: "%K == %@", "list.title", title)).wrappedValue

The equivalent code for such a query using Realm is very similar, but it uses the @ObservedResults property wrapper rather than FetchRequest:

1@ObservedResults(
2 Reminder.self,
3 filter: NSPredicate(format: "%K == %@", "list.title", title),
4 sortDescriptor: SortDescriptor(keyPath: "title", ascending: true)) var reminders

#7. Migrate Your Users' Production Data

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

This can be a very complex issue. Depending on your app's functionality, as well as your users' circumstances, how you go about handling this can end up being very different each time.

We've seen two major approaches:

  • Once you've migrated your code 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.
  • If the user's data is replaceable—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. 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.

#SwiftUI Previews

As with Core Data, your SwiftUI previews can add some data to Realm so that it's rendered in the preview. However, with Realm it's a lot easier as you don't need to mess with contexts and view contexts:

1func bootstrapReminder() {
2 do {
3 let realm = try Realm()
4 try realm.write {
5 realm.deleteAll()
6 let reminder = Reminder()
7 reminder.title = "Do something"
8 reminder.notes = "Anything will do"
9 reminder.dueDate = Date()
10 reminder.priority = 1
11 realm.add(list)
12 }
13 } catch {
14 print("Failed to bootstrap the default realm")
15 }
16}
17
18struct ReminderListView_Previews: PreviewProvider {
19 static var previews: some View {
20 bootstrapReminder()
21 return ReminderListView()
22 }
23}

#Syncing Realm Data

Now that your application data is stored in Realm, you have the option to sync that data to other devices (including Android) using MongoDB Realm Sync. That same data is then stored in Atlas where it can be queried by web applications via GraphQL or Realm's web SDK.

This enhanced functionality is beyond the scope of this guide, but you can see how it can be added by reading the Building a Mobile Chat App Using Realm – Integrating Realm into Your App series.

#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.

In this guide, we've focussed on the code that needs to be changed to work with Realm, but you'll be pleasantly surprised at just how much Core Data boilerplate code you're able to simply delete!

If you've been having trouble getting Core Data working in your app, or you're looking for a way to sync data between platforms, 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!

If you've any questions or comments, then please let us know on our community forum.

If you have questions, please head to our developer community website where the MongoDB engineers and the MongoDB community will help you build your next big idea with MongoDB.

Rate this article
© 2021 MongoDB, Inc.

About

  • Careers
  • Legal Notices
  • Privacy Notices
  • Security Information
  • Trust Center
© 2021 MongoDB, Inc.