Sync a Core Data store with the CloudKit public database
Swift & UI 进阶 18m

将 Core Data 存储与 CloudKit 公共数据库同步

Sync a Core Data store with the CloudKit public database

2020年6月24日

在 Apple 官方观看视频

一句话判断

NSPersistentCloudKitContainer 现在支持同步到 CloudKit Public Database 了——这意味着你可以用一套 Core Data 代码同时实现「用户私有数据同步」和「共享公共数据分发」,适合做社交、内容聚合类 app。

这场 Session 讲了什么

iOS 13 引入了 NSPersistentCloudKitContainer,自动把 Core Data 同步到 iCloud 私有数据库。iOS 14 扩展了这个能力,允许你选择同步到 CloudKit 的 Public Database、Private Database 或两者兼有。

Session 详细讲解了如何配置 NSPersistentStoreDescription 来指定目标数据库类型、如何处理公共数据库的权限模型(任何人可读,仅开发者可写)、以及如何在同一个 Core Data Stack 中混合使用公共和私有数据库。

对于需要「全局共享内容 + 用户私有数据」的 app(比如菜谱分享、读书笔记),这个功能让你不用写任何后端代码就能实现数据分发。

值得深挖的点

公共数据库与私有数据库的分工

Private Database 是每个用户独立的,数据只有用户自己能看到。Public Database 是全应用共享的,所有用户看到同一份数据。Session 的设计模式是:用 Public Database 存放「全局内容」(如模板、预设、公共数据集),用 Private Database 存放「用户个人数据」(如用户的自定义内容、偏好)。两个数据库可以对应同一个 Core Data Model 的不同 Configuration。

Public Database 的写入限制

Public Database 默认只有开发者(通过 CloudKit Dashboard 或 Server-to-Server 认证)能写入。普通用户只能读。如果你的 app 需要用户提交内容到公共数据库,需要走 CloudKit Web Service 的服务端写入路径,不能直接在客户端写入。这是一个重要的架构约束。

代码片段

import CoreData

// 配置同时使用公共和私有数据库
func setupPersistentContainer() -> NSPersistentContainer {
    let container = NSPersistentCloudKitContainer(name: "MyAppModel")
    
    // 获取 store description
    let descriptions = container.persistentStoreDescriptions
    
    // 第一个 store:私有数据库(用户个人数据)
    let privateDescription = descriptions.first!
    privateDescription.configuration = "Private" // 对应 Core Data Model 中的 Configuration
    privateDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(
        containerIdentifier: "iCloud.com.example.myapp"
    )
    privateDescription.cloudKitContainerOptions?.databaseScope = .private
    
    // 第二个 store:公共数据库(共享内容)
    let publicDescription = NSPersistentStoreDescription(url: publicStoreURL())
    publicDescription.configuration = "Public"
    publicDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(
        containerIdentifier: "iCloud.com.example.myapp"
    )
    publicDescription.cloudKitContainerOptions?.databaseScope = .public
    
    container.persistentStoreDescriptions = [privateDescription, publicDescription]
    container.loadPersistentStores { _, error in
        if let error = error {
            fatalError("Core Data 加载失败: \(error)")
        }
    }
    return container
}
// 从公共数据库读取共享模板
func fetchPublicTemplates(context: NSManagedObjectContext) throws -> [Template] {
    let request: NSFetchRequest<Template> = Template.fetchRequest()
    // Template entity 属于 "Public" configuration
    request.sortDescriptors = [NSSortDescriptor(keyPath: \Template.name, ascending: true)]
    return try context.fetch(request)
}

// 保存用户私有数据
func savePrivateNote(title: String, content: String, context: NSManagedObjectContext) throws {
    let note = Note(context: context)
    // Note entity 属于 "Private" configuration
    note.title = title
    note.content = content
    note.createdAt = Date()
    try context.save()
    // 自动同步到用户的私有 iCloud 数据库
}

最佳实践

  1. 在 Core Data Model 中用 Configuration 分区。给公共和私有数据分别创建 Configuration,把不同的 Entity 分配到对应的 Configuration。这样 NSPersistentCloudKitContainer 会自动把不同 Configuration 的数据路由到不同的 CloudKit 数据库。
  2. 公共数据库的数据量要控制。Public Database 有严格的 API 配额限制(免费额度每天只有几万次请求)。把公共数据做成本地缓存,不要每次启动都从 CloudKit 拉取。
  3. 测试时用 CloudKit Development EnvironmentNSPersistentCloudKitContainer 默认连接 Development 环境。在发布前一定要切到 Production 环境做一轮完整的同步测试,Schema 变更在两个环境是独立的。

还有什么值得关注

  • NSPersistentCloudKitContainer 的同步日志可以在 Console.app 中通过 subsystem: com.apple.coredata 过滤查看,调试同步问题时很有用。
  • 公共数据库支持 CKQuerySubscription,可以用推送通知告知客户端公共数据有更新。
  • iOS 14 还新增了 NSPersistentStoreDescriptionsetOption 方法来配置自动迁移策略,减少 Schema 版本升级时的手动干预。
WWDC 2020