How does CloudKit work: containers, databases, zones, records, subscriptions, CKShare and sync?
Predict first
A record is created in the default zone with two fields. What do its identity and keys look like, and what are the account status values?
import CloudKit
let rec = CKRecord(recordType: "Note", recordID: CKRecord.ID(recordName: "n1", zoneID: CKRecordZone.default().zoneID))
rec["title"] = "Hi"
rec["count"] = 3
print(rec.recordType, rec.recordID.recordName, rec.allKeys().sorted(), rec["title"] as! String, rec.recordID.zoneID.zoneName)
print(CKAccountStatus.available.rawValue, CKAccountStatus.noAccount.rawValue)
- Note n1 ["count", "title"] Hi _defaultZone, then 1 3
- Note n1 ["title"] Hi default, then 0 1
- n1 Note [] Hi _defaultZone, then 3 1
Mental model. CloudKit is a shared filing cabinet the user already owns (iCloud). The private drawer is theirs, the public drawer is the town’s, and a CKShare hands a folder to another person with read or write rights.
CloudKit is Apple’s backend-as-a-service tied to the user’s iCloud account. A container (CKContainer, an identifier like iCloud.com.example.app, created in the capability) holds three databases: public (shared by all users, readable without an iCloud account, writes need one), private (per user, counts against the user’s iCloud storage, the right home for personal data) and shared (records others shared with this user). A database contains record zones (the default zone _defaultZone, or custom zones, which are required for features like atomic batch changes, change tokens and sharing) and records (CKRecord: a type, an id made of record name and zone, and typed fields set with subscripting such as record["title"] = "Hi", with references, assets and a size limit around 1 MB). The schema is defined in the CloudKit Console (a development and a production environment; deploy the development schema to production before release, and note that production schema changes are additive only). Operations use CKDatabase async methods or CKModifyRecordsOperation; handle the common errors (.networkUnavailable, .serverRecordChanged with the conflicting server record, .quotaExceeded, .notAuthenticated when the user has no iCloud account: check accountStatus(), where noAccount is 3 and available is 1) and retry on retryAfterSeconds.
Sync is the hard part. You need to know what changed: use custom zones with server change tokens (CKFetchRecordZoneChangesOperation returns only changes since the token), subscriptions (CKDatabaseSubscription and CKRecordZoneSubscription) that cause a silent push to wake the app, and conflict resolution (compare change tags and merge). Three levels of abstraction: raw CloudKit (most control, most code), CKSyncEngine (iOS 17: manages tokens, scheduling, batching and retries; you provide the local state and merge logic), and NSPersistentCloudKitContainer or SwiftData with CloudKit (mirrors a local store automatically, with the schema constraints noted in the persistence topic). Sharing: CKShare makes a record hierarchy (a root record and its children) shareable with other iCloud users, through UICloudSharingController or the SwiftUI sharing view, with participants, permissions (read-only or read-write) and a share URL; it needs a custom zone and the shared database on the participants’ side. Pitfalls: no iCloud account, quota limits, the dev-to-production schema deployment, rate limits, and CloudKit’s lack of server-side logic (no joins or arbitrary queries; index fields you query), so plan the data model for CloudKit rather than forcing a relational one. The multi-device conflict ideas are in the persistence topic.
A record has a type and an id within a zone (the default zone is named _defaultZone), and typed fields. Account status 1 is available and 3 is no account.
Common traps
- Forgetting to deploy the schema to production. The app works in development and fails for every App Store user.
- Assuming an iCloud account exists. Check
accountStatus()and degrade gracefully when it isnoAccount.
They might follow up with
- Which database for personal data? The private database, which counts against the user’s iCloud storage, not yours.
- What does
CKSyncEnginemanage for you? Change tokens, scheduling, batching and retries.
Related questions
- 17.7.1 How do you approach an unfamiliar Apple framework: what do you check before you write code, and how do you…
- 17.4.2 How does StoreKit 2 work: products, purchases, transaction verification, entitlements, refunds, and what must…
- 13.7.1 How would you design multi-device sync: identity, change tracking, conflict resolution, deletions and…
How does StoreKit 2 work: products, purchases, transaction… →