Core Data+CloudKit 未连接到 CloudKit
Core Data+CloudKit not connecting to CloudKit
我正在更新 iOS13 之前的 Core Data 应用程序以使用 Core Data+CloudKit 同步来支持多个设备上的单个用户。同步应该是自动发生的,并且在我开发的一个中间步骤中它确实起作用了。现在它不工作了,CloudKit 遥测没有注册任何 activity,我不明白为什么它不工作。
在我以前的应用程序版本中,我在UserDefaults中提供了少量标签字符串并允许用户修改它们,更新后的版本放回UserDefaults中。这是避免为永远只有少量对象管理第二个核心数据实体的捷径。
我已经意识到这在多设备实现中不起作用,因为本地 Core Data 数据库为空的事实不再意味着它是该用户的第一次使用。相反,每个新设备都需要查看基于云的数据源以找到用户正在使用的标签字符串,并且如果用户还没有其他东西,则只使用应用程序提供的默认值。
我遵循了 Apple 对 Setting Up Core Data with CloudKit 的说明,起初同步工作正常。但后来我意识到同步行为不正确,而不是从存储在 UserDefaults 中的字符串预填充我真的需要提供一个预填充的核心数据数据库(.sqlite 文件)。我实现了它,在第一次本地启动时复制捆绑的 .sqlite 文件后,该应用程序现在可以在本地正常运行。
但由于某种原因,此更改导致 CloudKit 同步停止工作。现在,我不仅没有在其他设备上获得任何自动更新,而且在 CloudKit 仪表板遥测中也没有得到任何结果,因此似乎 CloudKit 同步从未开始。这很奇怪,因为我在本地收到 'remote' 刚刚在本地发生的更改的通知(当我在本地保存新数据时,我在本地调用作为通知的#selector 列出的函数)。
我对 problem/solution 是什么感到困惑。这是我的相关代码。
//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+。
我认为新创建的 NSPersistentStoreDescription
默认情况下没有 cloudKitContainerOptions
。
要设置它们,请尝试:
storeDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: <<Your CloudKit ID>>)
我还发现您的实体的所有属性都必须是可选的,或者具有默认值,否则它将无法工作。如果文档中的内容更清楚,那就太好了...
我正在更新 iOS13 之前的 Core Data 应用程序以使用 Core Data+CloudKit 同步来支持多个设备上的单个用户。同步应该是自动发生的,并且在我开发的一个中间步骤中它确实起作用了。现在它不工作了,CloudKit 遥测没有注册任何 activity,我不明白为什么它不工作。
在我以前的应用程序版本中,我在UserDefaults中提供了少量标签字符串并允许用户修改它们,更新后的版本放回UserDefaults中。这是避免为永远只有少量对象管理第二个核心数据实体的捷径。
我已经意识到这在多设备实现中不起作用,因为本地 Core Data 数据库为空的事实不再意味着它是该用户的第一次使用。相反,每个新设备都需要查看基于云的数据源以找到用户正在使用的标签字符串,并且如果用户还没有其他东西,则只使用应用程序提供的默认值。
我遵循了 Apple 对 Setting Up Core Data with CloudKit 的说明,起初同步工作正常。但后来我意识到同步行为不正确,而不是从存储在 UserDefaults 中的字符串预填充我真的需要提供一个预填充的核心数据数据库(.sqlite 文件)。我实现了它,在第一次本地启动时复制捆绑的 .sqlite 文件后,该应用程序现在可以在本地正常运行。
但由于某种原因,此更改导致 CloudKit 同步停止工作。现在,我不仅没有在其他设备上获得任何自动更新,而且在 CloudKit 仪表板遥测中也没有得到任何结果,因此似乎 CloudKit 同步从未开始。这很奇怪,因为我在本地收到 'remote' 刚刚在本地发生的更改的通知(当我在本地保存新数据时,我在本地调用作为通知的#selector 列出的函数)。
我对 problem/solution 是什么感到困惑。这是我的相关代码。
//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+。
我认为新创建的 NSPersistentStoreDescription
默认情况下没有 cloudKitContainerOptions
。
要设置它们,请尝试:
storeDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: <<Your CloudKit ID>>)
我还发现您的实体的所有属性都必须是可选的,或者具有默认值,否则它将无法工作。如果文档中的内容更清楚,那就太好了...