mcpField
A
axiom-cloudkit-ref

axiom-cloudkit-ref

github.com/charleswiltgen数据与数据库1.2k194 次安装今天更新
代码仓库

关于 axiom-cloudkit-ref

axiom-cloudkit-ref 是面向 iOS 开发者的 CloudKit 结构化数据同步参考技能,涵盖 SwiftData、CKSyncEngine 和原始 API 的选择与实现,也涉及冲突、共享和监控。

主要功能

  1. 01比较三种 CloudKit 同步方案
  2. 02配置私有、公共和共享数据库
  3. 03处理同步状态与冲突
  4. 04设置记录共享和推送订阅
  5. 05监控请求、错误与延迟

适用场景

  1. 01为 SwiftData 应用配置 iCloud 同步
  2. 02为自定义持久化实现云同步
  3. 03选择数据库范围并实现协作共享
  4. 04排查同步问题并查看性能数据

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-cloudkit-ref

安装后在 Claude Code 中输入/axiom-cloudkit-ref确认已加载。

详细介绍

CloudKit 参考

用途:数据库式 iCloud 存储与同步的全面 CloudKit 参考 可用性:iOS 10.0+(基础功能)、iOS 17.0+(CKSyncEngine)、iOS 17.0+(SwiftData 集成) 上下文:通过 CKSyncEngine(WWDC 2023)或 SwiftData 集成实现现代 CloudKit 同步

何时使用此技能

在以下情况使用此技能:

  • 实现结构化数据与 iCloud 同步
  • 在 SwiftData+CloudKit、CKSyncEngine 或原始 CloudKit API 之间进行选择
  • 设置公共、私有或共享数据库
  • 实现冲突解决
  • 调试 CloudKit 同步问题
  • 监控 CloudKit 性能

不适用于:简单文件同步(请改用 axiom-icloud-drive-ref)

概览

CloudKit 用于结构化数据同步(带有关联关系的记录),而非简单文件同步。

三种现代方案:

  1. SwiftData + CloudKit(最简单,iOS 17+)
  2. CKSyncEngine(自定义持久化,iOS 17+,WWDC 2023)
  3. 原始 CloudKit API(控制力最强,但更复杂)

方案 1:SwiftData + CloudKit(推荐)

适用场景:使用 SwiftData 模型的 iOS 17+ 应用

限制:

  • 仅支持私有数据库(不支持公共/共享数据库)
  • 自动同步(控制力较弱)
  • CloudKit 同步不支持 @Attribute(.unique) — 使用 CloudKit 时请移除
  • 受 SwiftData 限制
// ✅ 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

所需授权:

  • iCloud 功能
  • CloudKit 容器

SwiftData 详情请使用 axiom-swiftdata 技能


方案 2:CKSyncEngine(现代方案,WWDC 2023)

适用场景:使用自定义持久化(SQLite、GRDB、JSON)并与云端同步

相比原始 CloudKit 的优势:

  • 自动管理获取/上传周期
  • 处理冲突
  • 管理账户变更
  • 比手动执行 CKDatabase 操作更受推荐
// ✅ 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: []
        )
    }
}

关键概念:

  • 状态序列化:在应用启动之间持久化同步状态
  • 事件:委托对象接收变更事件
  • 批次:你提供待处理的变更,由引擎上传
  • 自动冲突解决:引擎处理基本冲突

方案 3:原始 CloudKit API(旧版)

适用场景:仅当 CKSyncEngine 不适用时(少见)

核心类型:

  • CKContainer — 入口点
  • CKDatabase — 公共/私有/共享范围
  • CKRecord — 单条数据记录
  • CKRecordZone — 逻辑分组
  • CKAsset — 二进制文件存储
基本操作
// ✅ 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)
更新记录
// ✅ Fetch-then-modify-then-save (prevents serverRecordChanged errors)
let record = try await privateDatabase.record(for: recordID)
record["title"] = "Updated title"
record["isCompleted"] = true
try await privateDatabase.save(record)

// ✅ Batch modify (save + delete in one operation)
let operation = CKModifyRecordsOperation(
    recordsToSave: [updatedRecord1, updatedRecord2],
    recordIDsToDelete: [deletedID]
)
operation.perRecordSaveBlock = { recordID, result in
    switch result {
    case .success: print("Saved: \(recordID)")
    case .failure(let error): print("Failed: \(recordID) — \(error)")
    }
}
try await privateDatabase.add(operation)
冲突解决
// ✅ 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)

数据库范围

范围可访问性SwiftData 支持使用场景
私有仅用户本人✅ 支持用户个人数据
公共所有用户❌ 不支持共享/公共内容
共享受邀用户❌ 不支持协作
私有数据库
// ✅ 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 (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 (collaboration)
let sharedDB = CKContainer.default().sharedCloudDatabase

// For CKShare-based collaboration
// Users invited to specific record zones
// Use for: Shared documents, team data

CloudKit 资源(文件)

// ✅ 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)
}

重要:CKAsset 需要文件 URL,而非 Data。请先将数据写入临时文件。


CloudKit 控制台(监控 — WWDC 2024)

开发者通知

为以下情况设置提醒:

  • 架构变更
  • 超出配额
  • 错误率过高
  • 自定义阈值
遥测

监控:

  • 请求数量
  • 错误率
  • 延迟(p50、p95、p99)
  • 带宽用量
日志

查看:

  • 单个请求
  • 错误详情
  • 性能瓶颈

访问地址:https://icloud.developer.apple.com/dashboard


常见模式

模式 1:初始同步
// ✅ 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)
        }
    }
}
模式 2:增量同步
// ✅ 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)
}

授权

Xcode 中所需的授权:


<key>com.apple.developer.icloud-services</key>
<array>
    <string>CloudKit</string>
</array>


<key>com.apple.developer.icloud-container-identifiers</key>
<array>
    <string>iCloud.com.example.app</string>
</array>

设置步骤:

  1. Xcode → 目标 → 签名与功能
  2. “+ Capability” → iCloud
  3. 勾选 “CloudKit”
  4. 选择或创建容器

订阅(推送通知)

数据库订阅
// ✅ Get notified of ANY change in private database
let subscription = CKDatabaseSubscription(subscriptionID: "all-changes")

let notificationInfo = CKSubscription.NotificationInfo()
notificationInfo.shouldSendContentAvailable = true  // Silent push
subscription.notificationInfo = notificationInfo

try await privateDatabase.save(subscription)
查询订阅
// ✅ Get notified when records matching a query change
let predicate = NSPredicate(format: "priority > 3")
let subscription = CKQuerySubscription(
    recordType: "Task",
    predicate: predicate,
    subscriptionID: "high-priority-tasks",
    options: [.firesOnRecordCreation, .firesOnRecordUpdate, .firesOnRecordDeletion]
)

let notificationInfo = CKSubscription.NotificationInfo()
notificationInfo.alertBody = "High priority task changed"
notificationInfo.shouldBadge = true
subscription.notificationInfo = notificationInfo

try await privateDatabase.save(subscription)
区域订阅
// ✅ Get notified of any change in a specific zone
let zoneID = CKRecordZone.ID(zoneName: "Tasks")
let subscription = CKRecordZoneSubscription(
    zoneID: zoneID,
    subscriptionID: "tasks-zone"
)

let notificationInfo = CKSubscription.NotificationInfo()
notificationInfo.shouldSendContentAvailable = true
subscription.notificationInfo = notificationInfo

try await privateDatabase.save(subscription)
处理推送通知
// In AppDelegate
func application(_ application: UIApplication,
                 didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async -> UIBackgroundFetchResult {
    let notification = CKNotification(fromRemoteNotificationDictionary: userInfo)

    if notification.subscriptionID == "all-changes" {
        try? await fetchChanges(since: savedChangeToken)
        return .newData
    }
    return .noData
}

共享记录

创建共享
// ✅ Share a record with other users
let record = try await privateDatabase.record(for: recordID)

// Record must be in a custom zone (not default zone)
let share = CKShare(rootRecord: record)
share[CKShare.SystemFieldKey.title] = "Shared Task List"
share.publicPermission = .none  // Invite-only

// Save both the record and share together
let operation = CKModifyRecordsOperation(
    recordsToSave: [record, share],
    recordIDsToDelete: nil
)
try await privateDatabase.add(operation)
显示共享界面
import CloudKit
import UIKit

// ✅ UIKit sharing controller
let sharingController = UICloudSharingController(share: share, container: container)
sharingController.delegate = self
present(sharingController, animated: true)

// Delegate methods
extension ViewController: UICloudSharingControllerDelegate {
    func cloudSharingController(_ csc: UICloudSharingController,
                                failedToSaveShareWithError error: Error) {
        print("Share failed: \(error)")
    }

    func itemTitle(for csc: UICloudSharingController) -> String? {
        return "My Shared List"
    }
}
管理参与者
// ✅ Check participants
for participant in share.participants {
    print("\(participant.userIdentity.nameComponents?.givenName ?? "Unknown")")
    print("  Acceptance: \(participant.acceptanceStatus)")
    print("  Permission: \(participant.permission)")
    // .readOnly, .readWrite, .none
}

// ✅ Remove participant
share.removeParticipant(participant)
try await privateDatabase.save(share)
接受共享
// In SceneDelegate or AppDelegate
func userDidAcceptCloudKitShareWith(_ cloudKitShareMetadata: CKShare.Metadata) {
    let operation = CKAcceptSharesOperation(shareMetadatas: [cloudKitShareMetadata])
    operation.acceptSharesResultBlock = { result in
        switch result {
        case .success: print("Share accepted")
        case .failure(let error): print("Accept failed: \(error)")
        }
    }
    CKContainer(identifier: cloudKitShareMetadata.containerIdentifier)
        .add(operation)
}

快速参考

任务现代 API(iOS 17+)旧版 API
结构化数据同步SwiftData + CloudKitCKSyncEngine 或 CKDatabase
自定义持久化同步CKSyncEngineCKDatabase
冲突解决自动(SwiftData/CKSyncEngine)手动(savePolicy)
账户变更自动处理手动检测
监控CloudKit Console 遥测数据手动记录

相关技能

  • axiom-swiftdata — SwiftData 实现详情
  • axiom-storage — 选择 CloudKit 还是 iCloud Drive
  • axiom-icloud-drive-ref — 基于文件的 iCloud 同步
  • axiom-cloud-sync-diag — 调试 CloudKit 同步问题

最后更新:2025-12-12 技能类型:参考 最低 iOS 版本:10.0(基础功能),17.0(CKSyncEngine、SwiftData 集成) WWDC 场次:2023-10188(CKSyncEngine),2024-10122(CloudKit Console)

评论

支持 Markdown