关于 axiom-cloudkit-ref
axiom-cloudkit-ref 是面向 iOS 开发者的 CloudKit 结构化数据同步参考技能,涵盖 SwiftData、CKSyncEngine 和原始 API 的选择与实现,也涉及冲突、共享和监控。
主要功能
- 01比较三种 CloudKit 同步方案
- 02配置私有、公共和共享数据库
- 03处理同步状态与冲突
- 04设置记录共享和推送订阅
- 05监控请求、错误与延迟
适用场景
- 01为 SwiftData 应用配置 iCloud 同步
- 02为自定义持久化实现云同步
- 03选择数据库范围并实现协作共享
- 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 用于结构化数据同步(带有关联关系的记录),而非简单文件同步。
三种现代方案:
- SwiftData + CloudKit(最简单,iOS 17+)
- CKSyncEngine(自定义持久化,iOS 17+,WWDC 2023)
- 原始 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>
设置步骤:
- Xcode → 目标 → 签名与功能
- “+ Capability” → iCloud
- 勾选 “CloudKit”
- 选择或创建容器
订阅(推送通知)
数据库订阅
// ✅ 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 + CloudKit | CKSyncEngine 或 CKDatabase |
| 自定义持久化同步 | CKSyncEngine | CKDatabase |
| 冲突解决 | 自动(SwiftData/CKSyncEngine) | 手动(savePolicy) |
| 账户变更 | 自动处理 | 手动检测 |
| 监控 | CloudKit Console 遥测数据 | 手动记录 |
相关技能
axiom-swiftdata— SwiftData 实现详情axiom-storage— 选择 CloudKit 还是 iCloud Driveaxiom-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)