cloudkit

Apple CloudKit framework for iOS/macOS/watchOS/tvOS development. Use for iCloud data persistence, multi-device sync, CKSyncEngine implementation, CKContainer/CKDatabase/CKRecord operations, conflict resolution, error handling, subscriptions, and CloudKit best practices. Triggers on CloudKit questions, iCloud sync implementation, CKRecord CRUD, zone management, or cross-device data synchronization in Swift/SwiftUI apps.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "cloudkit" with this command: npx skills add subsc-taha/cloudkit-skill/subsc-taha-cloudkit-skill-cloudkit

CloudKit Framework Skill

CloudKit is Apple's framework for iCloud data persistence with up to 1PB public storage and automatic cross-device sync.

Code Review Checklist

When reviewing CloudKit code, verify:

  • Account status checked before private/shared database operations
  • Custom zones used (not default zone) for production data
  • All CloudKit errors handled with retryAfterSeconds respected
  • serverRecordChanged conflicts handled with proper merge logic
  • CKErrorPartialFailure parsed for individual record errors
  • Batch operations used (CKModifyRecordsOperation) not individual saves
  • Large binary data stored as CKAsset (records have 1MB limit)
  • Record keys type-safe (enums) not string literals
  • UI updates dispatched to main thread from callbacks
  • CKAccountChangedNotification observed for account switches
  • Subscriptions have unique IDs to prevent duplicates
  • CKShare uses custom zone (sharing requires custom zones)
  • CKSyncEngine state token cached on every .stateUpdate event
  • Schema deployed to production before App Store release

Review Output Format

Report issues as: [FILE:LINE] ISSUE_TITLE

Examples:

  • [SyncManager.swift:45] Missing CKSyncEngine state token persistence
  • [DataStore.swift:89] Unhandled serverRecordChanged conflict
  • [CloudKit.swift:156] Individual saves instead of batch operation

Quick Start

import CloudKit

// Initialize container and database
let container = CKContainer.default()  // or CKContainer(identifier: "iCloud.your.bundle.id")
let privateDB = container.privateCloudDatabase
let publicDB = container.publicCloudDatabase
let sharedDB = container.sharedCloudDatabase

Core Architecture

ComponentPurpose
CKContainerTop-level entry point (1 per app typically)
CKDatabaseStorage layer (private/public/shared)
CKRecordZoneLogical grouping of records in private DB
CKRecordSingle data item (like a dictionary)
CKRecord.IDUnique identifier (recordName + zoneID)
CKAssetBinary data (images, files)
CKReferenceRelationships between records
CKSubscriptionPush notification triggers

CKSyncEngine (iOS 17+) — Recommended Approach

CKSyncEngine dramatically simplifies sync. See references/cksyncengine.md for complete implementation guide.

Minimal Setup

import CloudKit

class SyncManager: CKSyncEngineDelegate {
    private var engine: CKSyncEngine!
    private let container = CKContainer(identifier: "iCloud.your.bundle.id")
    
    init() {
        let config = CKSyncEngine.Configuration(
            database: container.privateCloudDatabase,
            stateSerialization: loadCachedState(),  // nil if first launch
            delegate: self
        )
        engine = CKSyncEngine(config)
    }
    
    // MARK: - Delegate Methods
    
    func handleEvent(_ event: CKSyncEngine.Event, syncEngine: CKSyncEngine) async {
        switch event {
        case .stateUpdate(let update):
            // CRITICAL: Always cache the state token
            saveCachedState(update.stateSerialization)
            
        case .accountChange(let change):
            handleAccountChange(change)
            
        case .fetchedRecordZoneChanges(let changes):
            // Server → Local: Process incoming data
            for modification in changes.modifications {
                saveLocally(modification.record)
            }
            for deletion in changes.deletions {
                deleteLocally(deletion.recordID)
            }
            
        case .sentRecordZoneChanges(let sent):
            // Confirm successful uploads, handle failures
            for failure in sent.failedRecordSaves {
                handleSaveFailure(failure)
            }
            
        default: break
        }
    }
    
    func nextRecordZoneChangeBatch(_ context: CKSyncEngine.SendChangesContext, 
                                    syncEngine: CKSyncEngine) async -> CKSyncEngine.RecordZoneChangeBatch? {
        // Local → Server: Provide records to upload
        let pending = syncEngine.state.pendingRecordZoneChanges.filter { 
            context.options.scope.contains($0) 
        }
        return await CKSyncEngine.RecordZoneChangeBatch(pendingChanges: pending) { recordID in
            return getLocalRecord(for: recordID)
        }
    }
    
    // MARK: - Queue Changes
    
    func queueSave(_ record: CKRecord) {
        engine.state.add(pendingRecordZoneChanges: [.saveRecord(record.recordID)])
    }
    
    func queueDelete(_ recordID: CKRecord.ID) {
        engine.state.add(pendingRecordZoneChanges: [.deleteRecord(recordID)])
    }
}

CRUD Operations (Direct API)

For non-CKSyncEngine apps or public database. See references/crud-operations.md.

// CREATE
let record = CKRecord(recordType: "Note")
record["title"] = "My Note"
record["content"] = "Hello CloudKit"
let saved = try await database.save(record)

// READ
let recordID = CKRecord.ID(recordName: "unique-id")
let fetched = try await database.record(for: recordID)

// UPDATE
fetched["content"] = "Updated content"
let updated = try await database.save(fetched)

// DELETE
try await database.deleteRecord(withID: recordID)

// QUERY
let predicate = NSPredicate(format: "title BEGINSWITH %@", "My")
let query = CKQuery(recordType: "Note", predicate: predicate)
let (results, _) = try await database.records(matching: query)

Error Handling

See references/error-handling.md for complete error codes.

do {
    try await database.save(record)
} catch let error as CKError {
    switch error.code {
    case .serverRecordChanged:
        // Conflict! Resolve using serverRecord
        let serverRecord = error.serverRecord
        resolveConflict(local: record, server: serverRecord)
        
    case .networkFailure, .networkUnavailable, .serviceUnavailable:
        // Transient - retry with backoff
        let retryAfter = error.retryAfterSeconds ?? 30
        scheduleRetry(after: retryAfter)
        
    case .quotaExceeded:
        // User out of iCloud storage
        notifyUserStorageFull()
        
    case .notAuthenticated:
        // User not signed into iCloud
        promptiCloudSignIn()
        
    case .limitExceeded:
        // Too many records - split into batches of 400
        splitAndRetry(records)
        
    default:
        log("CloudKit error: \(error.localizedDescription)")
    }
}

Conflict Resolution

func resolveConflict(local: CKRecord, server: CKRecord?) -> CKRecord {
    guard let server = server else { return local }
    
    // Strategy 1: Server wins (safest)
    return server
    
    // Strategy 2: Last writer wins (by modificationDate)
    // return server.modificationDate! > local.modificationDate! ? server : local
    
    // Strategy 3: Field-level merge
    // let merged = CKRecord(recordType: local.recordType, recordID: local.recordID)
    // merged["title"] = local["title"]  // Keep local title
    // merged["content"] = server["content"]  // Keep server content
    // return merged
    
    // Strategy 4: Edit count (increment counter on each edit)
    // let localCount = local["editCount"] as? Int ?? 0
    // let serverCount = server["editCount"] as? Int ?? 0
    // return localCount > serverCount ? local : server
}

Record Zones

Private database supports custom zones with change tracking:

let zoneID = CKRecordZone.ID(zoneName: "MyAppZone", ownerName: CKCurrentUserDefaultName)
let zone = CKRecordZone(zoneID: zoneID)

// Create zone
try await database.save(zone)

// Create record in zone
let recordID = CKRecord.ID(recordName: UUID().uuidString, zoneID: zoneID)
let record = CKRecord(recordType: "Note", recordID: recordID)

// Delete zone (deletes ALL records in it)
try await database.deleteRecordZone(withID: zoneID)

Subscriptions & Push Notifications

// Subscribe to zone changes (private DB)
let subscription = CKRecordZoneSubscription(zoneID: zoneID)
let notificationInfo = CKSubscription.NotificationInfo()
notificationInfo.shouldSendContentAvailable = true  // Silent push
subscription.notificationInfo = notificationInfo
try await database.save(subscription)

// Subscribe to query (public DB)
let predicate = NSPredicate(format: "category == %@", "important")
let querySubscription = CKQuerySubscription(
    recordType: "Note",
    predicate: predicate,
    options: [.firesOnRecordCreation, .firesOnRecordUpdate]
)

Assets (Binary Data)

// Save image
let imageURL = FileManager.default.temporaryDirectory.appendingPathComponent("photo.jpg")
imageData.write(to: imageURL)
let asset = CKAsset(fileURL: imageURL)
record["photo"] = asset
try await database.save(record)

// Load image
if let asset = record["photo"] as? CKAsset, let url = asset.fileURL {
    let data = try Data(contentsOf: url)
    let image = UIImage(data: data)
}

Sharing (CloudKit Sharing)

// Create share
let share = CKShare(rootRecord: record)
share.publicPermission = .readOnly
share[CKShare.SystemFieldKey.title] = "Shared Document"

// Save both
let operation = CKModifyRecordsOperation(recordsToSave: [record, share])
try await database.add(operation)

// Present sharing UI
let sharingController = UICloudSharingController(share: share, container: container)
present(sharingController, animated: true)

Project Setup Checklist

  1. Apple Developer Program membership required
  2. Xcode Signing & Capabilities:
    • Add iCloud capability
    • Check CloudKit
    • Create/select container (e.g., iCloud.com.yourcompany.appname)
    • Background Modes → Remote notifications
  3. Container cannot be deleted — name carefully
  4. Info.plist (for background fetch):
    <key>UIBackgroundModes</key>
    <array>
        <string>remote-notification</string>
    </array>
    

CloudKit Dashboard

Access at: https://icloud.developer.apple.com

  • View/edit records, zones, subscriptions
  • Monitor usage and quotas
  • Deploy schema to production
  • Development vs Production environments are separate

Best Practices

  1. Always cache CKSyncEngine state token — or sync breaks
  2. Batch operations to 400 records max — avoid limitExceeded
  3. Store CKRecord metadata locally — for conflict resolution
  4. Use encrypted fields for sensitive datarecord.encryptedValues["key"]
  5. Handle all error cases — especially transient errors with retry
  6. Test on physical devices — simulator has limitations
  7. Don't use enums in synced data — use strings instead (forward compatibility)
  8. Keep change tokens after fetches — commit only after local save succeeds

References

Implementation Guides

Features

  • Sharing — CKShare, UICloudSharingController, zone sharing
  • User Discovery — CKUserIdentity, finding users, discoverability
  • Privacy & Security — Encryption, access controls, GDPR compliance

Reference

External Resources

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

Claw Insights Install

Install and run Claw Insights, a read-only observability dashboard that monitors your OpenClaw agent with zero intrusion — no code changes, no cloud dependen...

Registry SourceRecently Updated
Coding

QR Campaign Studio

Generate marketing QR codes with batch output, UTM tracking links, logo embedding, and poster composition. Use when users ask 生成二维码/批量二维码/渠道追踪码/带logo二维码. Sup...

Registry SourceRecently Updated
Coding

Seerr server manager

CLI for the Seerr media request management API. Search movies and TV shows, create and manage media requests, manage users, track issues, and administer a se...

Registry SourceRecently Updated
Coding

AI Corporate Training Video Factory — Build a Complete Employee Training Program in 2 Hours

Automates creation of complete corporate training programs with up-to-date curricula, professional videos, assessments, and automated employee delivery in 2...

Registry SourceRecently Updated