Sync to iCloud with CKSyncEngine
System & Services 进阶 20m

用 CKSyncEngine 同步 iCloud 数据:少写代码,多同步

Sync to iCloud with CKSyncEngine

2023年6月5日

在 Apple 官方观看视频

一句话判断

如果你不用 Core Data 但需要 CloudKit 同步,CKSyncEngine 帮你处理调度、推送通知、订阅管理和错误重试——你只管提供数据。

这场 Session 讲了什么

Tim 和 Aamer 从 CloudKit 团队出发,介绍了新的 CKSyncEngine API:

定位。CloudKit 同步有三个 API 层级:NSPersistentCloudKitContainer(全栈方案,包含本地持久化)、CKSyncEngine(自带本地持久化的同步引擎)、CKDatabase + CKOperations(完全手动)。CKSyncEngine 面向需要自带本地持久化方案的开发者。

核心工作流。App 告诉 SyncEngine 有待发送的变更 -> SyncEngine 提交任务给系统调度器 -> 调度器在合适时机运行任务 -> SyncEngine 向 App 批量请求变更数据 -> 发送到服务器 -> 回调结果。接收端:服务器推送通知 -> SyncEngine 自动监听 -> 获取变更 -> 交给 App 持久化。

批量处理。SyncEngine 按批次请求数据,避免一次性将大量记录载入内存。App 只在 SyncEngine 请求时才提供下一批数据。

已有实现的兼容性。CKSyncEngine 使用标准的 CloudKit record 和 zone 类型,可以和已有的自定义同步实现互通。Freeform 和 NSUbiquitousKeyValueStore 已经迁移到 CKSyncEngine。

测试和调试。提供了 CKSyncEngine 的测试支持,包括模拟不同网络条件和错误场景。

值得深挖的点

减少维护代码量。一个正确的自定义同步引擎需要数千行代码和双倍的测试代码。NSPersistentCloudKitContainer 背后有 70,000+ 行测试。CKSyncEngine 处理了调度、推送通知、账户变更、订阅管理、错误重试等所有公共逻辑,你只需要处理 App 特有的数据模型。

系统调度器的价值。SyncEngine 不直接发起网络请求,而是通过系统任务调度器。调度器会考虑设备状态(网络、电量、后台限制)来决定最佳同步时机。这意味着同步行为和系统其他部分协调一致,不会在不当的时机消耗资源。

向后兼容。NSUbiquitousKeyValueStore 是一个很好的例子——新版 OS 用 CKSyncEngine 实现,但仍然和旧版 OS 的数据兼容。如果你有现有的 CloudKit 同步实现,可以渐进式迁移。

代码片段

// CKSyncEngine 基本使用
// 1. 创建 SyncEngine
let syncEngine = CKSyncEngine(zoneConfiguration: .default)

// 2. 告知有待发送的变更
syncEngine.state.pendingChanges.append(
    CKSyncEngine.PendingDatabaseChange.save(record)
)

// 3. 处理 SyncEngine 的数据请求
syncEngine.nextBatchHandler = { context in
    // 根据上下文返回下一批变更
    let pendingChanges = syncEngine.state.pendingChanges
    let batch = pendingChanges.prefix(context.limit)
    return Array(batch)
}

// 4. 处理发送结果
syncEngine.sendChangesResultHandler = { result in
    switch result {
    case .success(let changes):
        // 从 pendingChanges 中移除已成功发送的变更
        syncEngine.state.pendingChanges.removeAll { change in
            changes.contains { $0.id == change.id }
        }
    case .failure(let error):
        // 根据错误类型决定重试或其他处理
    }
}

// 5. 处理接收到的变更
syncEngine.fetchChangesHandler = { changes in
    for change in changes {
        // 持久化到本地存储
        switch change {
        case .save(let record):
            localStore.save(record)
        case .delete(let recordID):
            localStore.delete(recordID)
        }
    }
}

最佳实践

  • 如果不需要自定义本地持久化,优先用 NSPersistentCloudKitContainer。
  • 批量提供变更数据,不要一次性加载所有待同步记录。
  • 妥善处理发送失败——某些错误需要重试,某些需要用户介入。
  • 利用 SyncEngine 的测试支持验证边界情况。
  • 如果现有实现工作良好,迁移不是必须的,但 CKSyncEngine 的维护成本更低。

还有什么值得关注

  • Freeform App 使用 CKSyncEngine 实现跨设备白板同步
  • NSUbiquitousKeyValueStore 已迁移到 CKSyncEngine,是向后兼容的范例
  • 系统调度器确保同步不会在不当时机消耗电量和流量
  • CKSyncEngine 会随着平台更新自动获得性能改进和新功能
WWDC 2023