💡 修复 iCloud 同步:使用 initializeCloudKitSchema

为您每周带来有关 Swift 和 SwiftUI 的精选资讯!

TL;DR:通过调用 initializeCloudKitSchema,确保 CloudKit 的 Schema 与本地数据模型一致,避免 Core Data 和 SwiftData 的 iCloud 同步不完整问题。首次启用同步或更新模型后调用一次即可。

问题背景

在 iOS 开发中,当使用 Core Data 或 SwiftData 实现 iCloud 数据同步时,开发者经常会遇到数据同步不完整的问题。某些操作能正常同步,而另一些新建的数据却无法同步到云端。这种情况通常与 CloudKit Schema 的创建有关。

为什么需要 initializeCloudKitSchema?

当你在 CloudKit Dashboard 中发现本地数据模型与云端 Schema 不一致时,很可能是因为没有正确使用 initializeCloudKitSchema 方法。虽然在模型简单的情况下,CloudKit 可能会在创建首条数据时自动生成 Schema,但当遇到以下情况时,自动创建往往会失败:

  • 数据模型包含复杂的关系
  • 首次创建数据时没有涉及所有关系对象
  • 模型结构发生更改

正确使用 initializeCloudKitSchema 的时机

需要在以下两种情况下使用 initializeCloudKitSchema

  1. 首次启用 iCloud 同步功能时
  2. 对本地数据模型进行修改后

Core Data 中的实现方式

在 Core Data 中,需要在 NSPersistentCloudKitContainer 加载完成后调用 initializeCloudKitSchema

Swift
let container = NSPersistentCloudKitContainer(name: "Model", managedObjectModel: mom)
container.persistentStoreDescriptions = [desc]
container.loadPersistentStores { _, err in
    if let err {
        fatalError(err.localizedDescription)
    }
}

// 在存储加载完成后初始化 CloudKit Schema
try container.initializeCloudKitSchema() // 执行后可注释此行

SwiftData 中的实现方式

SwiftData 的实现稍微复杂一些,需要先将 SwiftData 模型转换为 Core Data 的 NSManagedObjectModel

Swift
let config = ModelConfiguration()

do {
#if DEBUG
    try autoreleasepool {
        let desc = NSPersistentStoreDescription(url: config.url)
        let opts = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.com.example.Trips")
        desc.cloudKitContainerOptions = opts
        desc.shouldAddStoreAsynchronously = false
        
        if let mom = NSManagedObjectModel.makeManagedObjectModel(for: [Trip.self, Accommodation.self]) {
            let container = NSPersistentCloudKitContainer(name: "Trips", managedObjectModel: mom)
            container.persistentStoreDescriptions = [desc]
            container.loadPersistentStores { _, err in
                if let err {
                    fatalError(err.localizedDescription)
                }
            }
            
            try container.initializeCloudKitSchema()
            
            if let store = container.persistentStoreCoordinator.persistentStores.first {
                try container.persistentStoreCoordinator.remove(store)
            }
        }
    }
#endif
    modelContainer = try ModelContainer(for: Trip.self, Accommodation.self,
                                      configurations: config)
} catch {
    fatalError(error.localizedDescription)
}

注意事项

  1. initializeCloudKitSchema 只需在每次更新数据模型后执行一次
  2. 执行完成后,可以注释掉相关代码

延伸资料