CoreData + Cloudkit fetch() after app uninstall/reinstall returns 没有结果
CoreData + Cloudkit fetch() after app uninstall/reinstall returns no results
我正在尝试使用 NSPersistentCloudKitContainer
配置 CoreData+CloudKit 以自动同步数据 to/from CloudKit.
在 the Apple guide 之后,设置一个实体就足够了,在 Xcode 中添加适当的功能,在我的 AppDelegate 中设置 persistentContainer
和 saveContext
.
我正在通过 NSManagedObjectContext
进行 fetch()
和 save()
调用,并且我能够毫无问题地保存和获取记录。我可以在 CloudKit 仪表板中看到它们。
但是,如果我从模拟器中卸载应用程序并且 rebuild/reinstall,我的 fetchRequest
(没有 NSPredicate
或排序,只是获取所有记录)总是返回一个空的列表。我使用的是同一个 iCloud 帐户,我已经尝试了 public 和私有数据库范围。如果我创建一个新记录,然后重试我的提取请求,我可以检索新创建的记录,但不能检索任何旧记录。我 100% 确定这些记录仍在 CloudKit 数据库中,因为我可以在 CloudKit 仪表板网络应用程序上看到它们。
我查看了 Apple's CoreDataCloudKitDemo 应用程序,它能够在 uninstall/reinstall 之后从 CloudKit 数据库中获取“Post” 个实体,所以我知道有可能的。但是,它使用的是 NSFetchedResultsController
,这不适用于我的应用程序(我的是 SpriteKit 游戏)。
我试图将我的 CoreData+Cloudkit 代码复制到一个全新的 Xcode 项目中,我可以在那里重现这个问题。这是我的代码供参考:
import UIKit
import CoreData
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
lazy var persistentContainer: NSPersistentContainer = {
// Create a container that can load CloudKit-backed stores
let container = NSPersistentCloudKitContainer(name: "coredatacloudkitexample")
// Enable history tracking and remote notifications
guard let description = container.persistentStoreDescriptions.first else {
fatalError("###\(#function): Failed to retrieve a persistent store description.")
}
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
description.cloudKitContainerOptions?.databaseScope = .public
container.loadPersistentStores(completionHandler: { (_, error) in
guard let error = error as NSError? else { return }
fatalError("###\(#function): Failed to load persistent stores:\(error)")
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.transactionAuthor = "nibbler"
// Pin the viewContext to the current generation token and set it to keep itself up to date with local changes.
container.viewContext.automaticallyMergesChangesFromParent = true
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
}
return container
}()
}
// ------
import UIKit
import CoreData
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let viewContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
let people: [Person]
do {
people = try viewContext.fetch(fetchRequest)
print("---> fetched People from CoreData: \(people)")
// people.isEmpty is ALWAYS true (empty array) on first install of app, even if records exist in table in CloudKit
if people.isEmpty {
let person = Person(context: viewContext)
person.name = "nibbler"
// save the data through managed object context
do {
try viewContext.save()
print("--> created Person in CoreData: \(person)")
} catch {
print("---> failed to save Person: \(error.localizedDescription)")
}
}
} catch {
print("---> error: \(error)")
}
}
}
我错过了什么?为什么我只能获取此应用安装期间创建的记录,而不能获取之前的记录?
UPDATE:似乎如果我等待几秒钟并在我 am[=50= 的第一个应用安装时重新尝试获取] 能够从 CloudKit 数据库中检索结果。首次启动时,我还可以在控制台中看到大量 CoreData+CloudKit
日志消息。这就是我的想法——即使在使用 NSPersistentCloudKitContainer
时,一个 fetch()
是 reading/writing 到本地 CoreData 存储,然后一个单独的进程是 运行 在后台到镜像并将本地 CoreData 记录与 CloudKit 记录合并。
因此,我认为我需要以某种方式 wait/be 通知本地 CoreData 的 sync/merge 和 CloudKit 记录在我的 fetch()
首次启动时已完成调用,而不是在应用程序打开时立即进行 fetch()
调用。有什么想法吗?
你可以试试这个:
- 设置
NSPersistentCloudKitContainerOptions
let id = "iCloud.yourid"
let options = NSPersistentCloudKitContainerOptions(containerIdentifier: id)
description?.cloudKitContainerOptions = options
- 初始化您的 CloudKit 模式(这至少需要一次)
do {
try container.initializeCloudKitSchema()
} catch {
print("ERROR \(error)")
}
编辑:
你可以改变 lazy var persistentContainer: NSPersistentContainer
到lazy var persistentContainer: NSPersistentCloudKitContainer
这是@professormeowingtons 你提到的,每当你在模拟器上删除应用程序时,它不会显示以前的记录,所以我的建议是在已经配置了 iCloud 帐户的真实设备上试用你的应用程序,那这样你就可以向你的数据库添加一些记录,然后删除应用程序,重新安装并获取你之前输入的所有记录。
您需要使用 NSFetchedResultsController
,为什么您认为它不适用于您的应用程序?
有必要的原因是 NSFetchedResultsController
监视 viewContext
并且当同步过程下载新对象并将它们插入后台上下文时 automaticallyMergesChangesFromParent
将对象合并到viewContext
并推进代币。如果从获取的对象数组中插入、更新或删除对象,调用 FRC 的委托方法来通知您,这些对象是上下文中与获取请求的实体和谓词匹配的对象。
我正在尝试使用 NSPersistentCloudKitContainer
配置 CoreData+CloudKit 以自动同步数据 to/from CloudKit.
在 the Apple guide 之后,设置一个实体就足够了,在 Xcode 中添加适当的功能,在我的 AppDelegate 中设置 persistentContainer
和 saveContext
.
我正在通过 NSManagedObjectContext
进行 fetch()
和 save()
调用,并且我能够毫无问题地保存和获取记录。我可以在 CloudKit 仪表板中看到它们。
但是,如果我从模拟器中卸载应用程序并且 rebuild/reinstall,我的 fetchRequest
(没有 NSPredicate
或排序,只是获取所有记录)总是返回一个空的列表。我使用的是同一个 iCloud 帐户,我已经尝试了 public 和私有数据库范围。如果我创建一个新记录,然后重试我的提取请求,我可以检索新创建的记录,但不能检索任何旧记录。我 100% 确定这些记录仍在 CloudKit 数据库中,因为我可以在 CloudKit 仪表板网络应用程序上看到它们。
我查看了 Apple's CoreDataCloudKitDemo 应用程序,它能够在 uninstall/reinstall 之后从 CloudKit 数据库中获取“Post” 个实体,所以我知道有可能的。但是,它使用的是 NSFetchedResultsController
,这不适用于我的应用程序(我的是 SpriteKit 游戏)。
我试图将我的 CoreData+Cloudkit 代码复制到一个全新的 Xcode 项目中,我可以在那里重现这个问题。这是我的代码供参考:
import UIKit
import CoreData
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
lazy var persistentContainer: NSPersistentContainer = {
// Create a container that can load CloudKit-backed stores
let container = NSPersistentCloudKitContainer(name: "coredatacloudkitexample")
// Enable history tracking and remote notifications
guard let description = container.persistentStoreDescriptions.first else {
fatalError("###\(#function): Failed to retrieve a persistent store description.")
}
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
description.cloudKitContainerOptions?.databaseScope = .public
container.loadPersistentStores(completionHandler: { (_, error) in
guard let error = error as NSError? else { return }
fatalError("###\(#function): Failed to load persistent stores:\(error)")
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.transactionAuthor = "nibbler"
// Pin the viewContext to the current generation token and set it to keep itself up to date with local changes.
container.viewContext.automaticallyMergesChangesFromParent = true
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
}
return container
}()
}
// ------
import UIKit
import CoreData
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let viewContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
let people: [Person]
do {
people = try viewContext.fetch(fetchRequest)
print("---> fetched People from CoreData: \(people)")
// people.isEmpty is ALWAYS true (empty array) on first install of app, even if records exist in table in CloudKit
if people.isEmpty {
let person = Person(context: viewContext)
person.name = "nibbler"
// save the data through managed object context
do {
try viewContext.save()
print("--> created Person in CoreData: \(person)")
} catch {
print("---> failed to save Person: \(error.localizedDescription)")
}
}
} catch {
print("---> error: \(error)")
}
}
}
我错过了什么?为什么我只能获取此应用安装期间创建的记录,而不能获取之前的记录?
UPDATE:似乎如果我等待几秒钟并在我 am[=50= 的第一个应用安装时重新尝试获取] 能够从 CloudKit 数据库中检索结果。首次启动时,我还可以在控制台中看到大量 CoreData+CloudKit
日志消息。这就是我的想法——即使在使用 NSPersistentCloudKitContainer
时,一个 fetch()
是 reading/writing 到本地 CoreData 存储,然后一个单独的进程是 运行 在后台到镜像并将本地 CoreData 记录与 CloudKit 记录合并。
因此,我认为我需要以某种方式 wait/be 通知本地 CoreData 的 sync/merge 和 CloudKit 记录在我的 fetch()
首次启动时已完成调用,而不是在应用程序打开时立即进行 fetch()
调用。有什么想法吗?
你可以试试这个:
- 设置
NSPersistentCloudKitContainerOptions
let id = "iCloud.yourid"
let options = NSPersistentCloudKitContainerOptions(containerIdentifier: id)
description?.cloudKitContainerOptions = options
- 初始化您的 CloudKit 模式(这至少需要一次)
do {
try container.initializeCloudKitSchema()
} catch {
print("ERROR \(error)")
}
编辑:
你可以改变 lazy var persistentContainer: NSPersistentContainer
到lazy var persistentContainer: NSPersistentCloudKitContainer
这是@professormeowingtons 你提到的,每当你在模拟器上删除应用程序时,它不会显示以前的记录,所以我的建议是在已经配置了 iCloud 帐户的真实设备上试用你的应用程序,那这样你就可以向你的数据库添加一些记录,然后删除应用程序,重新安装并获取你之前输入的所有记录。
您需要使用 NSFetchedResultsController
,为什么您认为它不适用于您的应用程序?
有必要的原因是 NSFetchedResultsController
监视 viewContext
并且当同步过程下载新对象并将它们插入后台上下文时 automaticallyMergesChangesFromParent
将对象合并到viewContext
并推进代币。如果从获取的对象数组中插入、更新或删除对象,调用 FRC 的委托方法来通知您,这些对象是上下文中与获取请求的实体和谓词匹配的对象。