Core Data with CloudKit: The Basics
At WWDC 2019, Apple brought a major update to Core Data
by introducing NSPersistentCloudKitContainer
. This means that by using Core Data with CloudKit
, users can seamlessly access the data in their application on all their Apple devices without having to write a lot of code.
Core Data
provides powerful object graph management functionality for developing applications with structured data. CloudKit allows users to access their data on every device where they have logged into their iCloud account, while also providing a constantly available backup service. Core Data with CloudKit
combines the advantages of local persistence, cloud backup, and network distribution.
In 2020 and 2021, Apple continued to enhance Core Data with CloudKit
, adding functionality for syncing public databases and shared databases on top of the initial support for private database syncing.
In a series of blog posts, I will introduce the usage, debugging techniques, console settings, and attempt to delve deeper into its syncing mechanism of Core Data with CloudKit
.
The original article was written in Chinese and published on my blog 肘子的Swift记事本.
Part 2: Syncing Local Database to iCloud Private Database
Part3: Exploring the CloudKit Dashboard
Don’t miss out on the latest updates and excellent articles about Swift, SwiftUI, Core Data, and SwiftData. Subscribe to fatbobman’s Swift Weekly and receive weekly insights and valuable content directly to your inbox.
In light of the fact that my blog, Fatbobman’s Blog, now offers all articles in English, starting from April 1, 2024, I will no longer continue updating articles on Medium. You are cordially invited to visit my blog for more content.
Limitations of Core Data with CloudKit
Only runs on Apple’s ecosystem
Unlike other cross-platform solutions, Core Data with CloudKit
can only run on Apple's ecosystem and can only provide services to users within the Apple ecosystem.
High testing threshold
An Apple Developer Program account is required to access CloudKit
services and the development team's CKContainer
during the development process. In addition, the performance on the simulator is far less reliable than on a real device.
Advantages of Core Data with CloudKit
Almost free
Developers do not need to pay extra for network services. Private databases are stored in the user’s personal iCloud space, and the capacity of public databases automatically increases as the application user base grows, with a maximum storage of 1 PB, 10 TB of database storage, and 200 TB of traffic per day. However, Apple does take a 15–30% cut of app revenue, so it’s not completely free.
Security
On one hand, Apple ensures user data security through sandbox containers, database isolation, encrypted fields, and authentication. On the other hand, given Apple’s longstanding image as a privacy advocate among users, using Core Data with CloudKit
can increase user trust in your application.
In fact, it was seeing this feature at WWDC 2019 that gave me the original inspiration to develop HealthNotes — to ensure data privacy and to be able to save data for a long time.
High integration and good user experience
Authentication, distribution, and other processes are seamless, and users do not need to perform any additional logins to enjoy all the features.
Core Data
Core Data
was born in 2005, and its predecessor EOF
had gained considerable recognition from users as early as 1994. After years of evolution, Core Data
has become quite mature. As an object graph and persistence framework, almost every tutorial will tell you not to treat it as a database, nor as an ORM
.
The functions provided by Core Data
include, but are not limited to, managing serialized versions, managing object lifecycles, object graph management, SQL isolation, handling changes, persisting data, data memory optimization, and data querying.
Core Data
provides a wide range of features, but it is not very friendly to beginners and has a steep learning curve. In recent years, Apple has also noticed this problem and greatly reduced the difficulty of creating Stack
by adding PersistentContainer
. The emergence of SwiftUI
and Core Data templates
also allows beginners to use its powerful functions more easily in projects.
CloudKit
After Apple launched iCloud
, developers were unable to integrate their applications with iCloud
for several years. This problem was not resolved until Apple introduced the CloudKit
framework in 2014.
CloudKit
is a collection of services that includes databases, file storage, and user authentication systems, providing a way to move data between applications and iCloud containers
. Users can access data saved on iCloud
on multiple devices.
CloudKit
has significant differences in data types, internal logic, and Core Data
, which require some compromise or processing to convert data objects between the two. In fact, when CloudKit
was first released, developers strongly hoped for an easy conversion between the two. Prior to the release of Core Data with CloudKit
, third-party developers had already provided solutions for synchronizing Core Data
or other data objects (such as realm
) to CloudKit
, and most of these solutions are still supported.
With the previous release of the persistent history tracking feature, Apple finally provided its own solution, Core Data with CloudKit
, in 2019.
Core Data objects vs CloudKit objects
Both frameworks have their own basic object types, which cannot be directly mapped to each other. Here, we will briefly introduce and compare some of the basic object types involved in this article:
NSPersistentContainer vs CKContainer
NSPersistentContainer
creates and manages the persistent store coordinator (NSPersistentStoreCoordinator
) and managed object context (NSManagedObjectContext
) through handling the managed object model (NSManagedObjectModel
). Developers create its instance through code.
CKContainer
is similar to the application's sandbox logic, where structured data, files, and other resources can be saved. Each application using CloudKit
should have its own CKContainer
(through configuration, an application can correspond to multiple CKContainer
s, and a CKContainer
can also serve multiple applications). Developers usually do not create new CKContainer
instances directly in their code, but rather through the iCloud console
or by creating one in the Signing&Capabilities
of the Xcode Target
.
NSPersistentStore vs CKDatabase/CkRecordZone
NSPersistentStore
is the abstract base class for all Core Data
persistent stores, supporting four types of persistence (SQLite
, Binary
, XML
, and In-Memory
). In an NSPersistentContainer
, multiple NSPersistentStore
instances (which can be of different types) can be held through declaring multiple NSPersistentStoreDescription
s. NSPersistentStore
has no concept of user authentication, but can be set to read-only or read-write modes. As Core Data with CloudKit
requires support for persistent history tracking, only NSPersistentStore
instances with SQLite
as the storage type can be synchronized, and on the device, the instance of this NSPersistentStore
will point to a SQLite database file
.
On CloudKit
, structured data storage has only one type, but it is distinguished in two dimensions.
From the perspective of user authentication, CKDatabase
provides three forms of databases: private, public, and shared. Application users (who have logged in to their iCloud account) can only access their own private database, which is saved in their personal iCloud space and cannot be operated on by others. Data saved in the public database can be called by any authorized application, even if the app user has not logged in to their iCloud account, and the application can still read its contents. Application users can share some data with other users of the same app
, and the shared data will be placed in the shared database, and the sharer can set other users' read and write permissions for the data.
Data in CKDatabase
is not scattered together, but is placed in specified RecordZone
s. We can create any number of Zone
s in the private database (public and shared databases only support default Zone
). When a CKContainer
is created, a CKRecordZone
named _defaultZone
is generated by default in each database.
Therefore, when we save data to a CloudKit database, we not only need to specify the database (private, public, shared) type, but also need to indicate the specific zoneID
(no need to mark when saving to _defaultZone
).
NSManagedObjectModel vs Schema
NSManagedObjectModel
is a model for managed objects, which represents the data entities in Core Data
. In most cases, developers use Xcode's Data Model Editor
to define the model, which is saved in a .xcdatamodeld
file that contains information such as entity properties, relationships, indexes, constraints, validation, and configurations.
When CloudKit
is enabled in an application, a Schema
is created in the CKContainer
. The Schema
includes record types, relationships between record types, indexes, and user permissions.
In addition to creating the Schema
content directly in the iCloud
console, we can also create CKRecord
in the code, which will automatically create or update the corresponding content in the Schema
.
The Schema
has security role settings that can be set for world
, icloud
, and creator
to different read and write permissions.
Entities vs Record Types
Although we usually emphasize that Core Data
is not a database, entities are very similar to tables in a database. We describe objects in entities, including their names, properties, and relationships. Eventually, we describe them as NSEntityDescription
and compile them into NSManagedObjectModel
.
In CloudKit
, we describe the name and properties of the data object using Record Types
.
There is a lot of information that can be configured in Entity
, but Record Types
can only describe part of it. Since the two cannot be one-to-one correspondence, when designing data objects for Core Data with CloudKit
, we need to follow the relevant rules (specific rules will be discussed in the next article).
Managed Object vs CKRecord
A managed object is a model object that represents a persistent storage record. The managed object is an instance of NSManagedObject
or its subclass, and is registered in the managed object context (NSManagedObjectContext
). In any given context, the managed object corresponds to at most one instance of a given record in persistent storage.
In CloudKit
, each record is called a CKRecord
.
We do not need to worry about the creation process of the ID
(NSMangedObjectID
) of the managed object, as Core Data
handles everything for us. However, for CKRecord
, in most cases, we need to explicitly set the CKRecordIdentifier
for each record in the code. As the unique identifier of the CKRecord
, CKRecordIdentifier
is used to determine the unique location of the CKRecord
in the database. If the data is saved in a custom CKRecordZone
, we also need to specify it in CKRecord.ID
.
CKSubscription
CloudKit
is a cloud service that responds to data changes in private databases on different devices using the same iCloud account, or in public databases on devices using different iCloud accounts.
Developers create CKSubscriptions
on iCloud using CloudKit
. When there is a change in data in CKContainer
, the server checks whether the change satisfies the trigger condition of a CKSubscription
. When the condition is met, a remote notification is sent to the subscribed devices. This is why when we add the CloudKit
feature in the Signing & Capabilities
of the Xcode Target
, Xcode
automatically adds Remote Notification
.
In practical use, three subclasses of CKSubscription
are used for different subscription tasks:
CKQuerySubscription
pushes a notification when aCKRecord
meets a setNSPercidate
.CKDatabaseSubscription
subscribes and tracks the creation, modification, and deletion of records in a database (CKDatabase
). This subscription can only be used for private databases and shared databases' customCKRecordZone
s and only notifies thesubscription creator
. In future articles, we will see howCore Data with CloudKit
uses this subscription in a private library.CKRecordZoneNotification
executes when the user or, in some cases,CloudKit
modifies the record in the record zone (CKRecordZone
). For example, when the value of a field in the record changes.
For the remote notifications pushed by the iCloud server, the application needs to respond in the Application Delegate
. In most cases, the remote notification
can be in the form of a silent notification
. Therefore, developers need to enable Background Modes
' Remote notifications
in their applications.
Guess on the Implementation of Core Data with CloudKit
Combining the basic knowledge introduced above, let us try to speculate on the implementation process of Core Data with CloudKit
using private database synchronization as an example:
Initialization:
- Create
CKContainer
. - Configure
Schema
based onNSManagedObjectModel
. - Create a
CKRecordZone
in the private database with the IDcom.apple.coredata.cloudkit.zone
. - Create a
CKDatabaseSubscription
on the private database.
Data export (export local Core Data
data to the cloud):
NSPersistentCloudKitContainer
creates a background task that responds to theNSPersistentStoreRemoteChange
notification ofpersistence history tracking
.- Based on the
transaction
ofNSPersistentStoreRemoteChange
, convertCore Data
operations intoCloudKit
operations. For example, for newly added data, convertNSManagedObject
instances toCKRecord
instances. - Pass the converted
CKRecord
or otherCloudKit operations
to the iCloud server throughCloudKit
.
Server-side:
- Process
CloudKit operation data
submitted by remote devices in order. - Check whether the operation causes data changes in the
com.apple.coredata.cloudkit.zone
of the private database according to theCKDatabaseSubscription
created during initialization. - Distribute remote notifications to all devices (
same iCloud account
) that subscribed toCKDatabaseSubscription
.
Data Import (Syncing Remote Data to Local)
- Use
NSPersistentCloudKitContainer
to create a background task that responds to the Cloud's silent push notifications. - Send a refresh request to the Cloud with the token of the last operation.
- Based on the token of each device, the Cloud returns the changes made to the database since the last refresh.
- Convert the remote data into local data by deleting, updating, and adding items, etc.
- Since the
automaticallyMergesChangesFromParent
property of the view context is set to true, the changes in local data will automatically reflect in the view context.
The above steps omit all technical difficulties and details, and only describe the general process.
Summary
In this article, we briefly introduced some basic knowledge about Core Data
, CloudKit
, and Core Data with CloudKit
. In the next article, we will explore how to use Core Data with CloudKit
to achieve synchronization between local and private databases.
If you found this article helpful or enjoyed reading it, consider making a donation to support my writing. Your contribution will help me continue creating valuable content for you.
Donate via Patreon, Buy Me aCoffee or PayPal.
Want to Connect?
@fatbobman on Twitter.