Record Duplication Issue When Migrating NSPersistentCloudKitContainer Persistent Store Location

Hi there!

I built an app using Core Data and CloudKit using NSPersistentCloudKitContainer. However, now that I'm looking to add support for extensions, I need to migrate the existing store from its default location using migratePersistentStore(_:to:options:withType:) so it can be accessed from a new App Group.

Once I update the app to the new build with the migration logic, the store appears empty to CloudKit and the existing objects are re-downloaded to the device. This effectively duplicates all of the records.

It seems as though the migrated records are new NSManagedObjects, which would explain why CloudKit doesn't have a corresponding CKRecord. Upon detecting that the CloudKit version of the object does not exist on device, NSPersistentCloudKitContainer ensures that it is created (appearing to the user as a duplicate).

Is there a way to handle this apparent duplication or to link up the migrated local records to the corresponding CKRecords? Any advice on ensuring that this migration goes smoothly for users with existing app data in iCloud?

This is how I currently have my container set up:
Code Block
let container = NSPersistentCloudKitContainer(name: containerName)
var defaultURL: URL?
if let storeDescription = container.persistentStoreDescriptions.first, let url = storeDescription.url {
    defaultURL = FileManager.default.fileExists(atPath: url.path) ? url : nil
}
/* If the file does not exist, set the description to use the shared store */
let description: NSPersistentStoreDescription
if let defaultURL = defaultURL {
    description = NSPersistentStoreDescription(url: defaultURL)
} else {
    description = NSPersistentStoreDescription(url: appGroupURL)
}
description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: cloudContainerId)
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { ... }
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container 

After creating the container, I run the method to migrate the store if the old store location was used (i.e. if the defaultURL is not null). Once the migration is complete, I re-create the NSPersistentContainer using the above function, which will use the shared appGroupURL since the store at the defaultURL was deleted. Shortly after setting the new container, the records are fetched from CloudKit automatically, which is where I'm running into my issue.

Thanks!

Accepted Reply

Don't do that. migratePersistentStore is doing exactly what you asked it to, creating a clean copy of all the data in your store file in a new location on the file system. Use replacePersistentStore instead to move the store to a new location.

Replies

Don't do that. migratePersistentStore is doing exactly what you asked it to, creating a clean copy of all the data in your store file in a new location on the file system. Use replacePersistentStore instead to move the store to a new location.
This worked perfectly. Thanks so much!
Would you be able to share the code you've written inside
Code Block
container.loadPersistentStores

I'm in the same situation as you, and my code is pretty much identical, but after I've replaced the persistent store, my app crashes, and I get the error:

"This NSPersistentStoreCoordinator has no persistent stores (unknown). It cannot perform a save operation.".