问题
I am trying to perform history tracking in my CoreData+CloudKit project which uses NSPersistentCloudKitContainer
. I have been following along with Apple's sample project
I want to perform certain task when the remote store has been updated. For this apple recommends enabling remote notification in the Signing & capabilities's Background Mode section of the app.
I have enabled History Tracking for my project as shown in Apple's sample project.
// turn on persistent history tracking
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber,
forKey: NSPersistentHistoryTrackingKey)
// ...
Also I have registered my store to listen for store changes.
// turn on remote change notifications
let remoteChangeKey = "NSPersistentStoreRemoteChangeNotificationOptionKey"
description?.setOption(true as NSNumber,
forKey: remoteChangeKey)
// ...
Observer is also added to listen for NSPersistentStoreRemoteChangeNotification
.
However there is no NSPersistentStoreRemoteChangeNotification
being fired. To make sure there is no mistake in my implementation, I am have simply put breakpoints in @objc func storeRemoteChange(_ notification: Notification)
the Apple's provided sample code but still I can not see any notification being fired and no breakpoints are activated.
I have understood the deduplication of the Tags done in the sample project and also tried testing it but without any success. Is it a bug in the Apple's implementation or am I missing any setup which is required?
回答1:
My guess is you are observing the container instead of the store coordinator, add your observer like this:
NotificationCenter.default.addObserver(
self, selector: #selector(type(of: self).storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator)
Note the last param container.persistentStoreCoordinator
And a warning, this notification comes in on all different threads so you be careful with concurrency. Just put a 5 second sleep in the method and you'll see on app launch 3 different threads call it. This is likely why in the example there is a historyQueue
with maxOperationCount
1 to handle it.
Some notifications have NSPersistentHistoryTokenKey
in the userInfo
not sure why.
回答2:
Debugging the sample app mentioned by the OP, I observed the following:
- As of XCode Version 11.3 (11C29), there are SDK constants both for the option key (
NSPersistentStoreRemoteChangeNotificationPostOptionKey
) and for the notification name (.NSPersistentStoreRemoteChange
), and these are reflected in the latest download of the sample code. - The sample app registers for the remote change notifications on the wrong object, so it never receives any. Changing the sender as per the accepted answer fixes this.
- The app UI always updates to reflect changes received from the cloud, but those updates are prompted not by remote change notifications but by the app's
NSFetchedResultsController
delegate using thecontrollerDidChangeContent
callback to refresh the UI. - The standard
NSPersistentCloudKitContainer
used by the sample app is doing automatic imports into the local persistent store of all the cloud-sent updates and, because the persistentStore is set up for history tracking and the viewContext is set up to auto-update to the latest generation of data, each import triggers a UI update.
Based on these observations, I wrote a small app from scratch based on the XCode template you get by specifying use of CoreData, CloudKit, and SwiftUI. I set up its persistent container and view context the same way they are set up in the sample app, and used SwiftUI's @FetchRequest
wrapper to obtain the data in the master view display. Sure enough, I saw the exact same remote import behavior without using any remote change notifications, and the UI updated after each import.
I then confirmed that, as per the accepted answer, if I registered for remote change notifications correctly, they would be received. They seem to be sent after each receive and import operation in the NSPersistentCloudKit completes. Observing them is not needed to get notifications of the local data changes initiated by those imports.
回答3:
I don't know whether it's a bug. Simply downloading and running the Apple's Sample Project but the NSPersistentStoreRemoteChangeNotification
is never fired.
I added one more observer for the same NSPersistentStoreRemoteChangeNotification
in my AppDelegate and it is firing.
I added notification observer in AppDelegate and then simply call the StoreRemoteChange(_:)
of the CoreDataStack. Also, Tag deduplication logic works properly.
Here is the code which I added in 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)
}
来源:https://stackoverflow.com/questions/59158513/nspersistentstoreremotechangenotification-not-getting-fired