我正在尝试在使用NSPersistentCloudKitContainer
的CoreData + CloudKit项目中执行历史记录跟踪。我一直在关注苹果的sample project
我想在远程存储已更新时执行某些任务。对于此苹果,建议在应用程序的“签名和功能”的“后台模式”部分中启用远程通知。
我已经为我的项目启用了历史跟踪功能,如Apple的示例项目所示。
// turn on persistent history tracking
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber,
forKey: NSPersistentHistoryTrackingKey)
// ...
我还注册了商店以收听商店更改。
// turn on remote change notifications
let remoteChangeKey = "NSPersistentStoreRemoteChangeNotificationOptionKey"
description?.setOption(true as NSNumber,
forKey: remoteChangeKey)
// ...
还添加了观察者以监听NSPersistentStoreRemoteChangeNotification
。
但是,没有NSPersistentStoreRemoteChangeNotification
被解雇。为了确保我的实现没有错误,我只是在Apple提供的示例代码的@objc func storeRemoteChange(_ notification: Notification)
中设置了断点,但是仍然看不到任何通知被触发并且没有断点被激活。
我已经了解了示例项目中对Tag的重复数据删除,并且还尝试对其进行测试,但没有成功。是Apple实施中的错误,还是我缺少所需的任何设置?
答案 0 :(得分:8)
我的猜测是,您正在观察容器而不是商店协调员,请添加观察者,如下所示:
NotificationCenter.default.addObserver(
self, selector: #selector(type(of: self).storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator)
注意最后一个参数container.persistentStoreCoordinator
还有一个警告,该通知出现在所有不同的线程上,因此请谨慎使用并发。只需在方法中放置5秒钟的睡眠,您就会在应用程序启动时看到3个不同的线程调用它。这可能是为什么在示例中有一个historyQueue
为1的maxOperationCount
处理它的原因。
有些通知在NSPersistentHistoryTokenKey
中有userInfo
,不确定为什么。
答案 1 :(得分:2)
我能够在项目中的两台设备之间通过iCloud可靠地响应核心数据更改。但是我到达了需要访问变更历史的地步。苹果公司很好地描述了在Consuming Relevant Store Changes
中进行设置的步骤我一直很高兴地将相关代码复制并粘贴到我的应用程序中。但是NSPersistentStoreRemoteChange通知没有通过。与喜剧一样,时间就是一切。根据{{3}}我
如果要配置自定义持久性存储描述,则您 必须在调用之前设置此属性 loadPersistentStores(completionHandler:)
我正在配置loadPersistentStores(completionHandler :)内部的persistentStoreStoreDescriptions(内部)。因此,痛苦的显而易见的方法是在AppDelegate中设置以下代码。
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentCloudKitContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentCloudKitContainer(name: "yourProjectNameGoesHere")
// turn on persistent history tracking
// https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber,
forKey: NSPersistentHistoryTrackingKey)
// turn on remote change notifications
let remoteChangeKey = "NSPersistentStoreRemoteChangeNotificationOptionKey"
description?.setOption(true as NSNumber,
forKey: remoteChangeKey)
// this will make background updates from iCloud available to the context.
container.viewContext.automaticallyMergesChangesFromParent = true
// call this LAST, after the persistentStoreDescriptions configuration.
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
从视图控制器或模型中捕获通知。
init() {
NotificationCenter.default.addObserver(self,
selector: #selector(fetchChanges),
name: .NSPersistentStoreRemoteChange,
object: pc.persistentStoreCoordinator)
}
@objc func fetchChanges(note: Notification) {
print("Just received a NSPersistentStoreRemoteChange notification")
}
答案 2 :(得分:1)
调试OP提到的示例应用程序,我观察到以下内容:
NSPersistentStoreRemoteChangeNotificationPostOptionKey
)和通知名称(.NSPersistentStoreRemoteChange
)都有SDK常量,这些常量反映在最新版的DLL中。示例代码。NSFetchedResultsController
委托使用controllerDidChangeContent
回调刷新UI提示的。 / li>
NSPersistentCloudKitContainer
正在将所有云发送的更新自动导入到本地持久存储中,并且由于将persistentStore设置为用于历史跟踪并且将viewContext设置为auto -更新为最新一代的数据,每次导入都会触发UI更新。基于这些观察,我基于指定通过使用CoreData,CloudKit和SwiftUI获得的XCode模板,从头开始编写了一个小应用程序。我以在示例应用程序中设置的方式来设置其持久性容器并查看上下文,并使用SwiftUI的@FetchRequest
包装器在主视图显示中获取数据。果然,我使用任何远程更改通知都看到了完全相同的远程导入行为 ,并且每次导入后都更新了UI。
然后我确认,按照接受的答案,如果我正确注册了远程更改通知,则将收到它们。它们似乎是在NSPersistentCloudKit中的每个接收和导入操作完成后发送的。不需要观察它们即可获得由这些导入引发的本地数据更改的通知。
答案 3 :(得分:0)
我不知道这是否是错误。只需下载并运行Apple的示例项目,但NSPersistentStoreRemoteChangeNotification
就不会被触发。
我在AppDelegate中为同一NSPersistentStoreRemoteChangeNotification
添加了一个观察者,并且它正在触发。
我在AppDelegate中添加了通知观察器,然后只需调用CoreDataStack的StoreRemoteChange(_:)
。同样,标签重复数据删除逻辑也可以正常工作。
这是我在AppDelegate中添加的代码
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// The view controller hierarchy is defined in the main storyboard.
guard let splitViewController = window?.rootViewController as? UISplitViewController,
let navController = splitViewController.viewControllers[splitViewController.viewControllers.count - 1] as? UINavigationController,
let topViewController = navController.topViewController else {
return false
}
// Configure the splitViewController.
topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
splitViewController.delegate = self
splitViewController.preferredDisplayMode = .allVisible
// Observe Core Data remote change notifications.
NotificationCenter.default.addObserver(
self, selector: #selector(type(of: self).storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange, object: nil)
return true
}
@objc
func storeRemoteChange(_ notification: Notification) {
coreDataStack.storeRemoteChange(notification)
}
答案 4 :(得分:0)
SwiftUI
这是一种在 SwiftUI 视图中收到 CloudKit 远程更改通知的方法,例如,更新依赖于 @FetchRequest 的 List 的内容——未在代码中显示简单:
struct MyView: View {
@State var refresh = UUID()
var didRemoteChange = NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange).receive(on: RunLoop.main)
var body: some View {
List {
// ...
}
.id(refresh)
.onReceive(self.didRemoteChange) { _ in
self.refresh = UUID()
}
}
}
注意: .receive(on: RunLoop.main)
是必要的,以避免从后台线程修改 UI,因为远程事件可能(并且将会)从后台线程触发。或者,也可以使用 .receive(on: DispatchQueue.main)
。
为此,需要设置 NSPersistentCloudKitContainer
以在发生远程更改时触发事件:
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "YourApp")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
//
// Generate notifications upon remote changes
//
container.persistentStoreDescriptions.forEach {
$0.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
}