Home iOS & Swift Tutorials

Getting Started with Core Data and CloudKit

In this tutorial, you’ll learn how to use NSPersistentCloudKitContainer to integrate Core Data and CloudKit.

4.8/5 6 Ratings

Version

  • Swift 5, iOS 14, Xcode 12

When it comes to persisting data in your app, Core Data is a great choice. It’s one of the oldest and most established Apple frameworks available, having been around since OS X Tiger and iOS 3.

Core Data syncing across devices has a shakier history, however. Apple deprecated the original iCloud sync API in iOS 10 and didn’t replace it until iOS 13, when it introduced NSPersistentCloudKitContainer. NSPersistentCloudKitContainer provides a bridge between your Core Data store and CloudKit, Apple’s framework for iCloud data storage.

In this tutorial, you’ll integrate CloudKit and Core Data in an app called PlaceTag, which lets you save a title, description and image for places you visit.

Specifically, you’ll learn how to:

  • Switch from old Core Data in iCloud to the modern Core Data and CloudKit.
  • Set up CloudKit in your project.
  • Simplify Core Data stack initialization.
  • Query records in the CloudKit Dashboard.
Note: This tutorial assumes a working knowledge of Core Data, object graphs, data persistence frameworks and iCloud. For more information about the Core Data framework, see Getting Started with Core Data.

Additionally, iCloud and CloudKit need a paid Apple developer account to function. You won’t be able to follow along if you aren’t enrolled in the Apple Developer Program or Apple Developer Enterprise Program.

Getting Started

To kick things off, download the starter project for this tutorial using the Download Materials button at the top or bottom of this page. Then, open the starter project in Xcode.

Select a development team, since this step involves setting up data against your developer ID, so select your (paid) team to continue. Update the bundle ID to be something unique within your organization. Finally, select an iCloud container name. This value should be unique and start with iCloud. Apple suggests that it be of the form iCloud.{reverse DNS}.{app name or name for group}. For instance: iCloud.com.yourcompany.yourapp.

Build and run. You’ll see an empty list, ready for your dear diary moments!

PlaceTag app showing an empty list and an Add Place button

Tap Add Place button and create an entry. If you want, you can attach an image from your photo library.

Add Place screen in PlaceTag with a name, notes and an image

Tap Save Place when you’re done and the place will appear in the list.

PlaceTag list with an entry for Iceland

You may have noticed that Xcode gives you a warning when building the project, saying that NSPersistentStoreUbiquitousContentNameKey was deprecated in iOS 10.0. That’s because this app already syncs Core Data via iCloud, but uses the old, deprecated method. You’ll look at that code next, before you update to the new syncing system.

Core Data Stack Initialization

In this tutorial, you’ll convert the app from the old Core Data iCloud sync to the new CloudKit system. A lot of this work will happen in one file. Open CoreDataStack.swift and check out init():

private init() {
  //1
  guard let modelURL = Bundle.main
    .url(forResource: "PlaceTag", withExtension: "momd") else {
    fatalError("Error loading model from bundle")
  }

  //2
  guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
    fatalError("Error initializing mom from: \(modelURL)")
  }

  //3
  let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)

  //4
  context = NSManagedObjectContext(
    concurrencyType: .mainQueueConcurrencyType)

  //5
  context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

  //6
  context.persistentStoreCoordinator = psc

  do {
    //7
    try psc.addPersistentStore(
      ofType: NSSQLiteStoreType,
      configurationName: nil,
      at: CoreDataStack.storeURL,
      options: CoreDataStack.storeOptions)
  } catch {
    fatalError("Error adding store: \(error)")
  }
}

If you’re working on an app that’s been around for a long time, you might have similar code to this for setting up your Core Data stack. Here’s a step by step explanation of what this code does.

  1. Get the URL of the compiled version of PlaceTag’s data model file. The uncompiled version of this file is available in the project as PlaceTag.xcdatamodeld.
  2. Initialize an instance of NSManagedObjectModel using the URL you got in step 1.
  3. Create an instance of NSPersistentStoreCoordinator from the managed object model.
  4. Initialize the context of CoreDataStack using the NSManagedObjectContext initializer and passes its concurrency type as mainQueueConcurrencyType. This will bind the managed object context to the main queue.
  5. Set the merge policy of context to NSMergeByPropertyObjectTrumpMergePolicy. Core data automatically synchronizes the merge conflicts between objects and overwrites old object, which is Persistent Store’s version of the object, with the new, in-memory, one.
  6. Set the Persistent Store Coordinator of context with the one you created in step 3.
  7. Finally, add a persistent store to the Persistent Store Coordinator, which makes initialization of the Core Data stack complete. If you don’t hit any of the error blocks, Core Data is now ready to use.

The storeOptions class property contains the deprecated NSPersistentStoreUbiquitousContentNameKey mentioned earlier. This tells the system to save the SQLite file inside the iCloud Document directory for the app in a ubiquity container.

Phew! That was a lot of steps. You’re hoping the new system is easier, right? Before you update the app, it’s time to learn about how the new way differs from the old.

How Core Data in iCloud Used to Work

iCloud has three systems for storing user data:

  1. Key-value Storage: This lets you save discrete values such as simple app states or preferences.
  2. Document Storage: Use this to persist user-visible file-based information such as drawings and complex app states.
  3. CloudKit: For managing structured data and sharing data among users.

The first iteration of Core Data in iCloud on iOS worked on top of document storage and employed the same iCloud APIs.

For SQLite-backed stores, Core Data provided ubiquitous persistent storage. It used the SQLite transactional persistence mechanism, located in your app’s ubiquity container.

Each instance of your app, on every device attached to an iCloud account, maintained its own local Core Data store file. In other words, when data changed locally, Core Data wrote change log files to your app’s default ubiquity container.

Similarly, when a change log arrived from another device attached to the same iCloud account, Core Data updated your app’s local copy of the SQLite database.

As interesting as it looked, this implementation never worked reliably. Developers had to find workarounds for issues to make a working syncing solution. These issues made Apple realize that this wasn’t going to work.

When Apple introduced iOS 10, it deprecated Core Data in iCloud and NSPersistentStoreUbiquitousContentNameKey and suggested that developers use CloudKit, instead. That left developers with a choice of using deprecated API or rolling their own CloudKit-backed sync.

Core Data and CloudKit Today

As of iOS 13 and Xcode 11, the Xcode template for Core Data projects has an option to integrate CloudKit as well.

Xcode New Project screen with the options to Use Core Data and Host in CloudKit selected

Core Data and CloudKit both have three main elements in their definition: objects, models and stores. In short, models describe objects and stores are where objects are persisted.

Look at the following table:

Table comparing terms in Core Data with terms in CloudKit

You’re familiar with the items in the Core Data column. In CloudKit nomenclature, you can map the concepts of NSManagedObject into CKRecord, NSManagedObjectModel into Schema and NSPersistentStore into CKRecordZone or CKDatabase.

According to a session in WWDC 2019, Apple engineers wrote thousands of lines of code to encapsulate common patterns used for syncing, scheduling and error recovery to make Core Data work with CloudKit. Fortunately, this time, Apple did a robust job.

Preparing PlaceTag for CloudKit

It’s time to move to the new Core Data CloudKit syncing method.

Setting up the Project

In Project navigator, click the PlaceTag project. Select the PlaceTag target and click the Signing & Capabilities tab item, as indicated by numbers 1 through 3 in the screenshot below.

Xcode Project setup screen with annotations for elements you'll use soon. Indexed arrows.

Deselect iCloud Documents and select CloudKit, as number 4 in the screenshot above indicates.

Many apps and users have access to iCloud. However, partitions called containers segregate and encapsulate the data to keep it private.

Apps that are already using CloudKit can’t use Core Data and CloudKit with their existing CloudKit containers. To fully manage all aspects of data mirroring, Core Data owns the CloudKit schema created from the Core Data model. Existing CloudKit containers aren’t compatible with this schema.

With that in mind, you need to create a new container. This value should be unique and start with iCloud. As mentioned before, Apple suggests that it be of the form iCloud.{reverse DNS}.{app name or name for group}. For instance: iCloud.com.yourcompany.yourapp.

It’s worth noting that multiple apps from a company may access a single container.

Unfortunately, Apple doesn’t let you delete containers later, so choose the container name wisely.

After you’ve chosen the name, select the container, if Xcode didn’t select it for you automatically.

Adding Invisible Push Notification Support

Whenever a record changes in the container, CloudKit sends a silent push notification to registered devices.

For this functionality to work, you need to add two capabilities to the app. Click the + button as indicated by number 5 in the image above and add Push Notification, if Xcode didn’t add it for you automatically.

Adding push notifications to your PlaceTag Xcode project setup

Then add Background Modes.

Adding background modes to your PlaceTag Xcode project setup

In the Background Modes section, select Remote Notifications.

Xcode's Background Modes screen with Remote notifications selected.

The beauty of Core Data and CloudKit integration is that the system handles all the work required to listen for and respond to remote notifications. If you use CloudKit without Core Data, you’ll have to do some extra steps, which are outside the scope of this tutorial, to achieve this.

Migrating Existing Data

Now it’s time to tie your Core Data stack to the CloudKit container you just created. Open CoreDataStack.swift.

The first thing you need to do is to update and move the existing database file. With the old sync system, your SQLite file was in a special location and in a slightly different format. NSPersistentStoreCoordinator can handle moving and updating it for you. Add the following method:

func migrateIfRequired(_ psc: NSPersistentStoreCoordinator) {
  //1
  if FileManager.default.fileExists(atPath: CoreDataStack.storeURL.path) {
    return
  }

  do {
    //2
    let store = try psc.addPersistentStore(
      ofType: NSSQLiteStoreType,
      configurationName: nil,
      at: CoreDataStack.storeURL,
      options: CoreDataStack.storeOptions)
    //3
    let newStore = try psc.migratePersistentStore(
      store,
      to: CoreDataStack.storeURL,
      options: [NSPersistentStoreRemoveUbiquitousMetadataOption: true],
      withType: NSSQLiteStoreType)
    //4
    try psc.remove(newStore)
  } catch {
    print("Error migrating store: \(error)")
  }
}

Here’s what this code is doing:

  1. You’ll call this method every time the app starts up, but you only need to migrate once. If the new store had already been created, your work is done. You might wonder why you’re using the same store URL as you did before. With the iCloud sync you were using, the database file isn’t actually stored at the URL you give it, but within a complicated set of folders at the same location. With the new system, the database file will live at the given URL.
  2. You create the persistent store just like you did before, with the same options. This store is the old iCloud-synced one.
  3. You tell the persistent store coordinator to migrate the old store to the given URL, removing the iCloud-related metadata while it does so. This is another deprecated key, so you’ll get another warning, but you can’t remove deprecated functionality without using deprecated code!
  4. Finally, you remove the persistent store you just created from the coordinator. This is because migration is only going to happen once, so the actual setup of the new persistent store will happen outside this method, and you shouldn’t have two persistent stores linked to the same file.

Modernizing the Core Data Stack

Now you have migration ready to go, you can update to a more modern Core Data stack. As you may have hoped, the initialization process has been streamlined. How streamlined, you ask? Well, go ahead and delete the whole init().

Add a lazy property of type NSPersistentContainer:

private lazy var persistentContainer: NSPersistentContainer = {
  //1
  let container = NSPersistentContainer(name: "PlaceTag")
  //2
  migrateIfRequired(container.persistentStoreCoordinator)
  //3
  container.persistentStoreDescriptions.first?.url = CoreDataStack.storeURL
  //4
  container.loadPersistentStores { _, error in
    if let error = error as NSError? {
      fatalError("Unresolved error \(error), \(error.userInfo)")
    }
  }
  return container
}()

Here’s what you’re doing with this code:

  1. NSPersistentContainer does a lot of the work that init() was doing before, including owning the persistent store coordinator, loading the model and owning the managed object context. It will automatically look for and load a data model with the name you give it at initialization.
  2. Call your new migration code, passing the persistent store coordinator owned by the container.
  3. Tell the container to use a specific URL for the database file. This step is optional, but you need it here because you’ve migrated the store, so you want to have control over the file location.
  4. Call loadPersistentStores(_:). This method loads or creates the backing SQLite storage file using the provided model and saves it to the appropriate location on disk. This is when you link the persistent store to the persistent store coordinator. If you hadn’t removed the migrated store earlier, you’d get errors at this point.

The persistent container owns the managed object context used for your UI, so next, you need to change the definition of context from a constant:

let context: NSManagedObjectContext

to a computed property:

var context: NSManagedObjectContext {
  persistentContainer.viewContext
}

You have now modernized your Core Data setup and removed the old iCloud sync.

Build and run. You’ll see the same data you added before.

PlaceTag list with an entry for Iceland

You can still add new places, just like before. But all your data is now local.

Moving to CloudKit

Apps that use Core Data can move to CloudKit as long as the persistent store is an NSSQLiteStoreType store, and the data model is compatible with CloudKit limitations. For example, CloudKit does not support unique constraints, undefined attributes or required relationships.

There’s a subclass of NSPersistentContainer called NSPersistentCloudKitContainer. This persistent container is capable of managing both CloudKit-backed and non-cloud stores.

By replacing NSPersistentContainer with NSPersistentCloudKitContainer, PlaceTag’s Core Data stack will be CloudKit-enabled. The transition is as soft and smooth as the cloud itself! That’s what you’ll do next.

Change the persistentContainer property to reflect these changes. Replace any NSPersistentContainer references with NSPersistentCloudKitContainer:

private lazy var persistentContainer: NSPersistentCloudKitContainer = {

and:

let container = NSPersistentCloudKitContainer(name: "PlaceTag")

Also add this line, just before you return the container:

container.viewContext.automaticallyMergesChangesFromParent = true

You don’t change much here — just the class and automaticallyMergesChangesFromParent. By setting this flag, the UI will reflect automatic syncing that happens through the silent push notification almost instantaneously.

Build and run. Nothing seems to change. However, this time, if you’re logged in to iCloud on the device you’re testing on, iCloud syncs the places you add to PlaceTag. You can verify this by running the app on two devices. Hooray!

PlaceTag showing the entry for Iceland in the list

Note: Make sure to test syncing on real devices. Simulators aren’t reliable when it comes to silent push notifications.

Viewing the Project in CloudKit Dashboard

The app should be syncing your places to CloudKit now, but your next step is to verify that it really is.

Open CloudKit Dashboard and log in with the same Apple ID you used on your test device. The CloudKit dashboard greets you.

PlaceTag's CloudKit Dashboard homepage

You’ll see the containers you created for your apps on the left and the available options for each container on the right.

Select the container for PlaceTag and click Data. The Records page will appear.

PlaceTag container in the CloudKit Dashboard's Records page

On this page, select com.apple.coredata.cloudkit.zone as the zone. Now, you can query the available data using filters.
Select the Query button and choose CD_Place for Type.

What is that CD_? CD stands for Core Data. Apple is doing the heavy lifting of converting Core Data to CloudKit objects. This prefix is part of their work to prevent namespace conflicts.

Under Filter, choose Custom and query the records that have a CD_Title of Iceland. If you created a place with another title, type it here.

Query Place in the PlaceTag CloudKit Dashboard

Click Save then Query Records.

The object you created in the app appears here. You can edit or remove the object and the changes will reflect in the app using the remote push notification you set up.

PlaceTag CloudKit Dashboard: Place Detail

Congratulations! You’ve successfully used CloudKit to convert PlaceTag so its data persists across multiple devices.

Where to Go From Here?

You can download the final project using the Download Materials button at the top or bottom of this tutorial.

You have successfully added CloudKit support to your Core Data app. Even though CloudKit is a sophisticated framework, Apple made it easy to use — at least, with Core Data.

As mentioned earlier, Core Data is a very old Apple framework. It has many features that are out of the scope of this tutorial. CloudKit, though relatively new, also has many interesting features.

With PlaceTag, you barely scratched the surface of Core Data and CloudKit. The entity in the model doesn’t have relationships with other entities. You didn’t go into custom notification handling and many other features.

There are lots of great resources for Core Data and CloudKit which you can use to explore this topic in more depth, including:

I hope you benefited from this tutorial. If you have any questions or comments, please join the discussion below!

Average Rating

4.8/5

Add a rating for this content

6 ratings

More like this

Contributors

Comments