Core Data with CloudKit: Exploring the CloudKit Dashboard

Published on

Get weekly handpicked updates on Swift and SwiftUI!

This article explores the CloudKit dashboard.

Introduction to the Dashboard

The CloudKit Dashboard requires an Apple Developer Program account. It’s accessible at https://icloud.developer.apple.com.

image-20210808161150623

Apple has significantly revamped the CloudKit Dashboard layout in the last two years. The screenshot above is from mid-2021.

The dashboard is divided into three sections:

  • Database (CloudKit Database)

    A web client for managing Schema, Record, Zone, user permissions, container environments, etc.

  • Telemetry (Telemetry)

    Provides a visual insight into server-side performance and the utilization of databases and push events.

  • Logs (Logs)

    Real-time and historical logs generated by CloudKit servers, showing interactions between the application and servers.

For most uses of Core Data with CloudKit, only a few features of the dashboard are needed (like environment deployment). However, CloudKit Dashboard helps understand the mechanisms behind Core Data data synchronization.

Database (CloudKit Database)

image-20210808163319683

In Core Data with CloudKit: The Basics, basic objects like CKContainer, CKDababase, CKZone, CKSubscription, CKRecord were discussed. This article will further introduce other objects and features of CloudKit.

Environments

CloudKit offers Development and Production environments for your app’s network data.

  • Development Environment

    During development, data generated through CloudKit is saved only in the development environment, accessible only to team members. Here, you can adjust Schema structure, modify Record Type properties, and reset the environment if necessary, similar to the pre-launch state of a Core Data application. Developers can thoroughly test the app with CloudKit services before offering them to users.

  • Production Environment

    When the app is ready for the App Store, the development environment’s structure must be deployed to the production environment (Deploy Schema Changes). Once deployed, modifications to the Schema in the production environment must be forward-compatible.

    The reason is straightforward: once an app is live, client update frequencies can’t be controlled. To ensure lower version clients can access data, any model changes need to be downward compatible.

    Apps sold on the App Store can only access the production environment.

Even if a developer’s account coincides with their personal iCloud account, development and production environments are separate sandboxes with no data overlap. When using Xcode for debugging, the app accesses the development environment, while versions downloaded through Testflight or the App Store access the production environment.

Deploying the development environment’s Schema to the production environment is done with Deploy Schema Changes.

image-20210808180259192

During deployment, modifications made since the last deployment are shown.

Even after deploying Schema to production, changes can still be made to the development environment and deployed again. If a model change doesn’t meet compatibility conditions, CloudKit Dashboard will prohibit deployment.

image-20210808175543219

Below the container name, the Schema deployment status is displayed. The first image shows the pre-deployment state, while the second shows the post-deployment state.

image-20210808180421055

image-20210808180014216

Before any operation, ensure you’re in the correct environment setting.

Given CloudKit’s environment deployment rules, be very cautious when designing the Core Data data model in projects using Core Data with CloudKit. My principle is to add, not subtract, not change. I will discuss how to migrate Core Data with CloudKit data models in a future article.

Security Roles

Security roles apply only to public databases.

CloudKit uses role-based access control (RBAC) to manage permissions and control access to data in public databases (private databases are unique to each app user). With CloudKit, you can set

permission levels for a role, then assign that role to a given record type (Record Type).

Permissions include read, write, and create. Read allows only reading records, write allows reading and writing, and create allows reading, writing, and creating new records.

CloudKit includes three preset roles: World (_world), Authenticated (_icloud), and Creator (_creator). World applies to anyone, regardless of iCloud status. Authenticated is for any authenticated iCloud user. Creator is the creator of the record (Record).

image-20210808210401070

The default setting allows anyone to read data, only authenticated iCloud users can create new records, and the creator can update their records.

image-20210809062640040

You can create custom security roles but cannot create user records (User Record). The system creates a user record for a user when they authenticate with the container for the first time. Existing users can be found and assigned to any custom role.

Security roles are part of the data model (Schema). Any security settings changes require deployment to the production environment to take effect. Once deployed, security roles cannot be deleted.

For most Core Data with CloudKit applications, the default system configuration suffices.

Indexes

CloudKit indexes come in three types:

  • Queryable (queryable)
  • Searchable (searchable)
  • Sortable (sortable)

After creating a Recored Type through CloudKit, you can create necessary indexes for each field (only NSString supports searchable). Index types are independent; for example, if you want a field to be both queryable and sortable, you need to create two separate indexes.

image-20210809064449042

Only after creating a queryable index for the recordName of a Record Type can you browse the data of that Type in Records.

image-20210809065509228

image-20210809064743215

Core Data with CloudKit automatically creates necessary indexes for every attribute of a managed object entity in CloudKit (excluding recordName). Unless you need to browse data in the CloudKit dashboard, there’s no need to add any indexes.

Record Types

Record Type is a type identifier specified by developers for CKRecord. You can create it directly in code or in the CloudKit dashboard.

image-20210809073043092

As mentioned in the Basics, while Entity has more configuration information than Record Type, the latter has a feature not present in Entity – metadata.

image-20210809075124786

CloudKit presets several metadata fields for each Record Type (even without any other fields created by developers). Each data record (CKRecord) contains this information, most of which are set automatically by the system.

  • createdTimestamp
  • createUserRecordName
  • _etag
  • modifiedTimestamp
  • modifiedUserRecordName
  • recordName

For certain Record Type types, the system adds specific metadata, like role, cloud.shared, etc.

This article focuses on Core Data with CloudKit. Let’s see how NSPersistentCloudKitContainer converts Core Data managed object attributes into CloudKit Recore Type fields.

image-20210809104558352

image-20210809104402659

The above image shows the Record Type for the Item in the template project from Synchronizing Local Database to iCloud Private Database in CloudKit. CloudKit automatically creates fields for each attribute of the managed object entity, mapping the attribute names to fields with CD_[attribute.name] keys. The field types

Translation: “These strings prefixed with CD_ will continuously appear in the console during the data synchronization process, and understanding their composition can be helpful for debugging code.”

Once a Record Type is deployed in the production environment, its fields cannot be deleted or renamed. As a result, certain operations in Core Data are not permissible in Core Data with CloudKit.

Do not rename entities or attributes in the data model of an application that has already been launched. This restriction applies even when using Mapping Model or Renaming ID. During development, if renaming is necessary, you may need to delete the app, reinstall, and reset the CloudKit development environment.

Zones

Each database type has a default Zone, and only private databases can have custom Zones.

image-20210809143010363

For data in private databases, you can assign a Zone when creating a CKRecord.

Swift
let zone = CKRecordZone(zoneName: "myZone")
let newStudent = CKRecord(recordType: "Student",
                          recordID: CKRecord.ID(recordName: UUID().uuidString,
                                                zoneID: zone.zoneID))

NSPersistentCloudKitContainer sets the ZoneID uniformly to com.apple.coredata.cloudkit.zone when converting managed objects to CKRecord. You must switch to the correct Zone to view the data.

image-20210809143648531

  • OWNER RECORD NAME: User record, corresponding to the _creator of the Zone.
  • CHANGE TOKEN: Comparison token.
  • ATOMIC: If set to true, the entire operation fails if CloudKit cannot update one or more records in the Zone.

Records

Used for browsing, creating, deleting, modifying, and querying data records.

image-20210809150327144

When browsing data, keep in mind:

  • Select the correct environment (development and production environments have completely different data).
  • Choose the right Database, Zone.
  • Ensure the Record Type metadata recordName has a queryable index.
  • Create corresponding indexes for fields if sorting or filtering is needed.
  • Indexes only take effect in the production environment after deployment.

Modifying Core Data mirror data in the CloudKit dashboard triggers immediate remote notifications and updates on the client side, though this practice is not recommended.

You can also access the CKRecord corresponding to a Core Data managed object in code:

Swift
func getLastUserID(_ object: Item?) -> CKRecord.ID? {
    guard let item = object else { return nil }
    guard let ckRecord = PersistenceController.shared.container.record(for: item.objectID) else { return nil }
    guard let userID = ckRecord.lastModifiedUserRecordID else {
        print("can't get userID")
        return nil
    }
    return userID
}

The above code retrieves the CKRecord of the managed object record and its last modified user.

Subscriptions

View CKSubscription registered on the container.

CKSubscriptions are created via code and can only be viewed or deleted on the dashboard. For example, the following code creates a CKQuerySubscription:

Swift
let predicate = NSPredicate(format: "name = 'bob'")
let subscription = CKQuerySubscription(recordType: "Student",
                                       predicate: predicate,
                                       options: [.firesOnRecordCreation])
let info = CKSubscription.NotificationInfo()
info.alertLocalizationKey = "create a new bob"
info.soundName = "NewAlert.aiff"
info.shouldBadge = true
info.alertBody = "hello world"

subscription.notificationInfo = info

publicDB.save(subscription) { subscription, error in
    if let error = error {
        print("error:\(error)")
    }
    guard let subscription = subscription else { return }
    print("save subscription successes:\(subscription)")
}

image-20210809154503445

NSPersistentCloudKitContainer registers a CKDatabaseSubscription for the private database mirror of Core Data. Remote notifications are pushed when data in com.apple.coredata.cloudkit.zone is updated.

image-20210809154946576

Tokens & Keys

Set API tokens for the container.

image-20210809152554058

Apart from manipulating data through code and the CloudKit dashboard, Apple also offers ways to access iCloud data from the web or other platforms. After obtaining tokens, developers can interact with data using CloudKit JS or CloudKit Web Services. Some developers have leveraged these services to create third-party libraries for accessing iCloud data on other platforms, like DroidNubeKit for accessing CloudKit on Android.

For the network mirror of Core Data, unless your data model is simple enough, this approach is not recommended. CloudKit Web services are better suited for data records created directly through CloudKit.

Sharing Fallbacks

Provides data record sharing fallback support for operating systems older than iOS 10 and macOS Sierra.

Telemetry

image-20210809161022705

Telemetry metrics help visualize performance when developing or updating your app. These include the number of requests, number of errors, push counts, server latency, average request size, etc. By setting a range, you can display data relevant to your needs, helping you better understand your application’s traffic configuration and usage trends.

Logs

image-20210809162346212

In the historical logs, you can view details including time, client platform version, users (anonymously), events, organizations, and more. While providing detailed information, CloudKit maintains the secrecy of user data as much as possible. The logs show server events for each user record in a container, without exposing any personal identity information. They only display anonymous, container-specific CloudKit users.

Analytics from AppStoreConnect are only from users who have agreed to share diagnostic and usage information with app developers, whereas CloudKit log information comes from all users of your app who use CloudKit services. Using both in conjunction provides better insights.

Summary

In most Core Data with CloudKit scenarios, developers rarely need to use the CloudKit dashboard. However, occasionally studying the data on the dashboard can be a fun experience.

In the next article, we will discuss some common situations encountered in developing Core Data with CloudKit projects, such as debugging, testing, data migration, etc.

I'm really looking forward to hearing your thoughts! Please Leave Your Comments Below to share your views and insights.

Fatbobman(东坡肘子)

I'm passionate about life and sharing knowledge. My blog focuses on Swift, SwiftUI, Core Data, and Swift Data. Follow my social media for the latest updates.

You can support me in the following ways