Claude-skill-registry axiom-cloudkit-ref
Use when implementing 'CloudKit sync', 'CKSyncEngine', 'CKRecord', 'CKDatabase', 'SwiftData CloudKit', 'shared database', 'public database', 'CloudKit zones', 'conflict resolution' - comprehensive CloudKit database APIs and modern sync patterns reference
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/axiom-cloudkit-ref" ~/.claude/skills/majiayu000-claude-skill-registry-axiom-cloudkit-ref && rm -rf "$T"
skills/data/axiom-cloudkit-ref/SKILL.mdCloudKit Reference
Purpose: Comprehensive CloudKit reference for database-based iCloud storage and sync Availability: iOS 10.0+ (basic), iOS 17.0+ (CKSyncEngine), iOS 17.0+ (SwiftData integration) Context: Modern CloudKit sync via CKSyncEngine (WWDC 2023) or SwiftData integration
When to Use This Skill
Use this skill when:
- Implementing structured data sync to iCloud
- Choosing between SwiftData+CloudKit, CKSyncEngine, or raw CloudKit APIs
- Setting up public/private/shared databases
- Implementing conflict resolution
- Debugging CloudKit sync issues
- Monitoring CloudKit performance
NOT for: Simple file sync (use
axiom-icloud-drive-ref instead)
Overview
CloudKit is for STRUCTURED DATA sync (records with relationships), not simple file sync.
Three modern approaches:
- SwiftData + CloudKit (Easiest, iOS 17+)
- CKSyncEngine (Custom persistence, iOS 17+, WWDC 2023)
- Raw CloudKit APIs (Maximum control, more complexity)
Approach 1: SwiftData + CloudKit (Recommended)
When to use: iOS 17+ apps with SwiftData models
Limitations:
- Private database only (no public/shared)
- Automatic sync (less control)
- SwiftData constraints apply
// ✅ CORRECT: SwiftData with CloudKit sync import SwiftData @Model class Task { var title: String var isCompleted: Bool var dueDate: Date init(title: String, isCompleted: Bool = false, dueDate: Date) { self.title = title self.isCompleted = isCompleted self.dueDate = dueDate } } // Configure CloudKit container let container = try ModelContainer( for: Task.self, configurations: ModelConfiguration( cloudKitDatabase: .private("iCloud.com.example.app") ) ) // That's it! Sync happens automatically
Entitlements required:
- iCloud capability
- CloudKit container
Use
skill for SwiftData detailsaxiom-swiftdata
Approach 2: CKSyncEngine (Modern, WWDC 2023)
When to use: Custom persistence (SQLite, GRDB, JSON) with cloud sync
Advantages over raw CloudKit:
- Manages fetch/upload cycles automatically
- Handles conflicts
- Manages account changes
- Recommended over manual CKDatabase operations
// ✅ CORRECT: CKSyncEngine setup import CloudKit class SyncManager { let syncEngine: CKSyncEngine init() throws { let config = CKSyncEngine.Configuration( database: CKContainer.default().privateCloudDatabase, stateSerialization: loadSyncState(), delegate: self ) syncEngine = try CKSyncEngine(config) } // Implement delegate methods } extension SyncManager: CKSyncEngineDelegate { // Handle events func handleEvent(_ event: CKSyncEngine.Event, syncEngine: CKSyncEngine) async { switch event { case .stateUpdate(let stateUpdate): saveSyncState(stateUpdate.stateSerialization) case .accountChange(let change): handleAccountChange(change) case .fetchedDatabaseChanges(let changes): applyDatabaseChanges(changes) case .fetchedRecordZoneChanges(let changes): applyRecordChanges(changes) case .sentRecordZoneChanges(let changes): handleSentChanges(changes) case .willFetchChanges, .didFetchChanges, .willSendChanges, .didSendChanges: // Optional lifecycle events break @unknown default: break } } // Next batch of changes to send func nextRecordZoneChangeBatch( _ context: CKSyncEngine.SendChangesContext, syncEngine: CKSyncEngine ) async -> CKSyncEngine.RecordZoneChangeBatch? { // Return pending local changes let pendingChanges = getPendingLocalChanges() return CKSyncEngine.RecordZoneChangeBatch( pendingSaves: pendingChanges, recordIDsToDelete: [] ) } }
Key concepts:
- State serialization: Persist sync state between app launches
- Events: Delegate receives events for changes
- Batches: You provide pending changes, engine uploads them
- Automatic conflict resolution: Engine handles basic conflicts
Approach 3: Raw CloudKit APIs (Legacy)
When to use: Only if CKSyncEngine doesn't fit (rare)
Core types:
— Entry pointCKContainer
— Public/private/shared scopeCKDatabase
— Individual data recordCKRecord
— Logical groupingCKRecordZone
— Binary file storageCKAsset
Basic Operations
// ✅ Container and database let container = CKContainer.default() let privateDatabase = container.privateCloudDatabase let publicDatabase = container.publicCloudDatabase // ✅ Create record let record = CKRecord(recordType: "Task") record["title"] = "Buy groceries" record["isCompleted"] = false record["dueDate"] = Date() // ✅ Save record try await privateDatabase.save(record) // ✅ Fetch record let recordID = CKRecord.ID(recordName: "task-123") let fetchedRecord = try await privateDatabase.record(for: recordID) // ✅ Query records let predicate = NSPredicate(format: "isCompleted == NO") let query = CKQuery(recordType: "Task", predicate: predicate) let (matchResults, _) = try await privateDatabase.records(matching: query) for result in matchResults { if case .success(let record) = result.1 { print("Task: \(record["title"] as? String ?? "")") } } // ✅ Delete record try await privateDatabase.deleteRecord(withID: recordID)
Conflict Resolution
// ✅ Handle conflicts with savePolicy let operation = CKModifyRecordsOperation( recordsToSave: [record], recordIDsToDelete: nil ) // Save only if server version unchanged operation.savePolicy = .ifServerRecordUnchanged // OR: Always overwrite server operation.savePolicy = .changedKeys // Only changed fields operation.modifyRecordsResultBlock = { result in switch result { case .success: print("Saved") case .failure(let error as CKError): if error.code == .serverRecordChanged { // Conflict - merge manually let serverRecord = error.serverRecord let clientRecord = error.clientRecord let merged = mergeRecords(server: serverRecord, client: clientRecord) // Retry with merged record } } } privateDatabase.add(operation)
Database Scopes
| Scope | Accessibility | SwiftData Support | Use Case |
|---|---|---|---|
| Private | User only | ✅ Yes | Personal user data |
| Public | All users | ❌ No | Shared/public content |
| Shared | Invited users | ❌ No | Collaboration |
Private Database
// ✅ Private database (most common) let privateDB = CKContainer.default().privateCloudDatabase // User must be signed into iCloud // Data syncs across user's devices // Not visible to other users
Public Database
// ✅ Public database (for shared content) let publicDB = CKContainer.default().publicCloudDatabase // Accessible to all app users // Even unauthenticated users can read // Writes require authentication // Use for: Leaderboards, public content, discovery
Shared Database
// ✅ Shared database (collaboration) let sharedDB = CKContainer.default().sharedCloudDatabase // For CKShare-based collaboration // Users invited to specific record zones // Use for: Shared documents, team data
CloudKit Assets (Files)
// ✅ Store files as CKAsset let imageURL = saveImageToTempFile(image) // Must be file URL let asset = CKAsset(fileURL: imageURL) let record = CKRecord(recordType: "Photo") record["image"] = asset record["caption"] = "Sunset" try await privateDatabase.save(record) // ✅ Retrieve asset let fetchedRecord = try await privateDatabase.record(for: recordID) if let asset = fetchedRecord["image"] as? CKAsset, let fileURL = asset.fileURL { let imageData = try Data(contentsOf: fileURL) let image = UIImage(data: imageData) }
Important: CKAsset requires a file URL, not Data. Write data to temp file first.
CloudKit Console (Monitoring - WWDC 2024)
Developer Notifications
Set up alerts for:
- Schema changes
- Quota exceeded
- High error rates
- Custom thresholds
Telemetry
Monitor:
- Request count
- Error rate
- Latency (p50, p95, p99)
- Bandwidth usage
Logs
View:
- Individual requests
- Error details
- Performance bottlenecks
Access: https://icloud.developer.apple.com/dashboard
Common Patterns
Pattern 1: Initial Sync
// ✅ Fetch all records on first launch func performInitialSync() async throws { let predicate = NSPredicate(value: true) // All records let query = CKQuery(recordType: "Task", predicate: predicate) let (results, _) = try await privateDatabase.records(matching: query) for result in results { if case .success(let record) = result.1 { saveToLocalDatabase(record) } } }
Pattern 2: Incremental Sync
// ✅ Use CKServerChangeToken for incremental fetches func fetchChanges(since token: CKServerChangeToken?) async throws { let zoneID = CKRecordZone.ID(zoneName: "Tasks") let config = CKFetchRecordZoneChangesOperation.ZoneConfiguration( previousServerChangeToken: token ) let operation = CKFetchRecordZoneChangesOperation( recordZoneIDs: [zoneID], configurationsByRecordZoneID: [zoneID: config] ) operation.recordWasChangedBlock = { recordID, result in if case .success(let record) = result { updateLocalDatabase(with: record) } } operation.recordWithIDWasDeletedBlock = { recordID, _ in deleteFromLocalDatabase(recordID) } operation.recordZoneFetchResultBlock = { zoneID, result in if case .success(let (token, _, _)) = result { saveChangeToken(token) // For next fetch } } try await privateDatabase.add(operation) }
Entitlements
Required entitlements in Xcode:
<!-- iCloud capability --> <key>com.apple.developer.icloud-services</key> <array> <string>CloudKit</string> </array> <!-- CloudKit container --> <key>com.apple.developer.icloud-container-identifiers</key> <array> <string>iCloud.com.example.app</string> </array>
Setup:
- Xcode → Target → Signing & Capabilities
- "+ Capability" → iCloud
- Check "CloudKit"
- Select or create container
Quick Reference
| Task | Modern API (iOS 17+) | Legacy API |
|---|---|---|
| Structured data sync | SwiftData + CloudKit | CKSyncEngine or CKDatabase |
| Custom persistence sync | CKSyncEngine | CKDatabase |
| Conflict resolution | Automatic (SwiftData/CKSyncEngine) | Manual (savePolicy) |
| Account changes | Handled automatically | Manual detection |
| Monitoring | CloudKit Console telemetry | Manual logging |
Related Skills
— SwiftData implementation detailsaxiom-swiftdata
— Choose CloudKit vs iCloud Driveaxiom-storage
— File-based iCloud syncaxiom-icloud-drive-ref
— Debug CloudKit sync issuesaxiom-cloud-sync-diag
Last Updated: 2025-12-12 Skill Type: Reference Minimum iOS: 10.0 (basic), 17.0 (CKSyncEngine, SwiftData integration) WWDC Sessions: 2023-10188 (CKSyncEngine), 2024-10122 (CloudKit Console)