核心数据+CloudKit无法连接到CloudKit

3
我正在更新一个使用 Core Data 的应用程序,使其使用 Core Data + CloudKit 同步来支持单个用户在多个设备上使用。同步应该自动进行,在开发过程的一个中间步骤中它确实起作用了。现在它没有起作用,CloudKit 遥测不记录任何活动,并且我无法弄清楚为什么它没有起作用。
在我的以前的应用程序版本中,我在 UserDefaults 中提供了一小部分标签字符串并允许用户修改它们,更新后的版本会放回到 UserDefaults 中。这是一种快捷方式,以避免必须管理第二个 Core Data 实体,因为它只会是少量的对象。
我后来意识到,在多设备实现中,这种方法行不通,因为本地 Core Data 数据库为空不再意味着它是该用户的首次使用。相反,每个新设备需要查找基于云的数据源以查找用户正在使用的标签字符串,只有当用户没有其他内容时才使用应用程序提供的默认值。
我按照 Apple 的说明进行了操作,详见 设置包含 CloudKit 的 Core Data,开始同步工作得很好。但是后来我意识到同步行为不正确,而且我真正需要的是提供一个预填充的 Core Data 数据库(.sqlite 文件),而不是从 UserDefaults 中存储的字符串预填充。我实现了这一点,并且在第一次本地启动时复制捆绑的 .sqlite 文件,现在应用程序在本地工作得很好。
但是由于某种原因,这个变化导致 CloudKit 同步停止工作。现在,我不仅在其他设备上没有获得任何自动更新,而且在 CloudKit 仪表板遥测中也没有结果,所以看起来 CloudKit 同步从未启动过。这很奇怪,因为在本地我正在收到“远程”更改的通知(当我在本地保存新数据时,我列出的选择器函数会在本地调用)。
我对问题/解决方案感到困惑。以下是我的相关代码。
//In my CoreDataHelper class
lazy var context = persistentContainer.viewContext

lazy var persistentContainer: NSPersistentCloudKitContainer = {

    let appName = Bundle.main.infoDictionary!["CFBundleName"] as! String
    let container = NSPersistentCloudKitContainer(name: appName)
    
    //Pre-load my default Core Data data (Category names) on first launch
    let storeUrl = FileManager.default.urls(for: .applicationSupportDirectory, in:.userDomainMask).first!.appendingPathComponent(appName + ".sqlite")
    let storeUrlFolder = FileManager.default.urls(for: .applicationSupportDirectory, in:.userDomainMask).first!

    if !FileManager.default.fileExists(atPath: (storeUrl.path)) {
        let seededDataUrl = Bundle.main.url(forResource: appName, withExtension: "sqlite")
        let seededDataUrl2 = Bundle.main.url(forResource: appName, withExtension: "sqlite-shm")
        let seededDataUrl3 = Bundle.main.url(forResource: appName, withExtension: "sqlite-wal")

        try! FileManager.default.copyItem(at: seededDataUrl!, to: storeUrl)
        try! FileManager.default.copyItem(at: seededDataUrl2!, to: storeUrlFolder.appendingPathComponent(appName + ".sqlite-shm"))
        try! FileManager.default.copyItem(at: seededDataUrl3!, to: storeUrlFolder.appendingPathComponent(appName + ".sqlite-wal"))
    }

    let storeDescription = NSPersistentStoreDescription(url: storeUrl)
    storeDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)

    //In the view controllers, we'll listen for relevant remote changes
    let remoteChangeKey = "NSPersistentStoreRemoteChangeNotificationOptionKey"
    storeDescription.setOption(true as NSNumber, forKey: remoteChangeKey)

    container.persistentStoreDescriptions = [storeDescription]
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    
    //This is returning nil but I don't think it should
    print(storeDescription.cloudKitContainerOptions?.containerIdentifier)
    
    return container
}()

//In my view controller

let context = CoreDataHelper.shared.context

override func viewDidLoad() {
    super.viewDidLoad()
    
    //Do other setup stuff, removed for clarity
    
    //enable CloudKit syncing
    context.automaticallyMergesChangesFromParent = true
}

override func viewWillAppear(_ animated: Bool) {
    clearsSelectionOnViewWillAppear = splitViewController!.isCollapsed
    super.viewWillAppear(animated)
    
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(reportCKchange),
        name: NSNotification.Name(
            rawValue: "NSPersistentStoreRemoteChangeNotification"),
        object: CoreDataHelper.shared.persistentContainer.persistentStoreCoordinator
    )
    updateUI()
}

@objc
fileprivate func reportCKchange() {
    print("Change reported from CK")
    tableView.reloadData()
}

注意:我已将目标更新为 iOS13+。

2个回答

5

我认为一个新创建的NSPersistentStoreDescription默认没有cloudKitContainerOptions

如果要设置它们,请尝试:

storeDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: <<您的 CloudKit ID>>)


Ch Ryder 再次挺身而出,感谢。 - tkhelm
现在CloudKit正在同步,我从Xcode控制台中得到了许多行同步过程的状态更新。你知道是否有可能减少这种冗长的输出吗? - tkhelm
我不建议隐藏这些日志,因为它们包含有用的、潜在相关的信息,但如果你真的想要这样做,可以参考这里:https://dev59.com/7lMH5IYBdhLWcg3w2kN3 - Ch Ryder
如果你需要Objective-C的等效代码,请使用以下代码:storeDescription.cloudKitContainerOptions = [[NSPersistentCloudKitContainerOptions alloc] initWithContainerIdentifier:@"iCloud.com.stuff"]; - neoscribe

1

我还发现,实体的所有属性必须是可选的,或者有默认值,否则它就不起作用。如果文档中能更清楚地说明这一点就好了...


是的,它说不支持可选的“关系”,但我也没有意识到所有实体“属性”也必须是可选的(或具有默认值)--但至少对于这个,在控制台中的错误消息是描述性的,希望是完整的... - Christopher King

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接