How to fix NSCloudKitMirroringDelegate unhandled exception after faulty model change

I have a complex data model in development mode, with a large amount of data, using CoreData with CloudKit sync. All worked fine, syncing from a Mac to an iPad Pro, until I made some unwise changes to the model and a couple of relationships. I should have known, but was hurrying and not thinking clearly. The App is not on the App Store: it's for my own use.

Records are now not synced to CloudKit: _error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _importFinishedWithResult:importer:]- (1371): <PFCloudKitImporter: 0x6000005d8080>: Import failed with error: Error Domain=NSCocoaErrorDomain Code=134421 "Import failed because applying the accumulated changes hit an unhandled exception." UserInfo={NSLocalizedFailureReason=Import failed because applying the accumulated changes hit an unhandled exception., NSUnderlyingException=* -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]}_**

It seems that there's a queue of faulty (non-matching) updates, which I can find no way of purging. Perhaps I could disconnect the CloudKit syncing in the app, use the CloudKit console to reset the development environment, then reconnect CloudKit syncing in the app - but would that invoke a repopulation of the CloudKit data, or cause a deletion of all CoreData data?

I can, with a few days' work, reimport all of the data - but that would, I assume, need a newly named app so as to create a new CloudKit container.

Any thoughts/solutions would be appreciated.

Regards, Michaela

Replies

Have you filed a feedback report with your store files and a sysdiagnose?

  • No, because this is a problem of my own making (i.e. an incorrect migration - worsened by an attempt to reverse the migration). I had thought of requesting developer support though: I think I still have entitlement for one.

  • Now requested developer support.

Add a Comment

I've created a test app, with CoreData records synced via CloudKit. Records created on the Mac get synced to the iPad Pro. It's a very simple app, just Listing records from a FetchedResults.

I then Reset development environment in the CloudKit console Web Portal, with both devices' app closed. This Reset, of course, deleted all records, schema and coredata.cloudkit zone from the Development environment.

On restarting each app, CoreData records on both devices remained and the app worked correctly, fetching existing records and also recreated the CloudKit zone, schema and records. Thereafter new records on the Mac were correctly synced to the iPad Pro.

I'll do some more testing, making the test app more complex with the sort of relationship I was trying to achieve in the messed-up app, then might be bold enough to use this approach to fix the problem app.

However, it would still be better if I could somehow purge the CloudKit update queue - although I can't be sure that the schema is correct anyway: probably not.

Regards, Michaela

  • Can you share the feedback report number?

  • I had assumed (hoped?) that resetting the development environment would clear any backlog of updates from the CloudKit sync, but this was not the case. The error still occurs and syncing stops once the reload from CoreData gets to the point where I'd made the faulty model changes. Reloading a correct schema into CloudKit doesn't work either. The update records must be persisting somewhere "behind the scenes" either in the Cloud or on my MacMini.

  • Resetting the development environment won't get you out of this state without also deleting the local store file on each device that has cached the data. The local view of the CloudKit data is invalid and NSPersistentCloudKitContainer works very hard to not lose data even when the development environment is reset.

Add a Comment

One root cause that we have identified for this issue is removing a many-to-many relationship from your model.

This is supposed to be a supported migration, so we are collecting feedback reports to track against that as a bug. If you would like to be notified of a fix for that share your feedback number here and we can relate them together.

As a workaround you can simply add the relationship back. Or, attempt a more detailed / invasive solution described below.

Our schema is public and described here:

https://developer.apple.com/documentation/coredata/mirroring_a_core_data_store_with_cloudkit/reading_cloudkit_records_for_core_data

  • Your container will contain CDMR records that identify the offending relationship.
    • The fields CD_entityNames and CD_relationships can be used to tell which relationship a record represents.
    • One (or more, depending on how many relationships you removed) of those records may be causing this issue.

You can choose to:

  1. Add the missing relationship back to your model
  2. Delete the offending records using a normal CKModifyRecords operation. On customer devices, you could query CloudKit using a CKQueryOperation for CDMR records that match the offending relationship and delete them. NSPersistentCloudKitContainer will consume those deletes on other devices as if they were changes it wrote.

One note of caution, #2 requires robust testing, as getting that wrong will prevent devices that still have the old model from making legitimate changes.

  • Unfortunately, the suggested approaches didn't fix the issue. As I said in the original post, I made some unwise changes to the model and a couple of relationships. I should have known, but was hurrying and not thinking clearly. This was some weeks ago and I'd parked the problem (was ill), so I can't remember what I did at the time. This also means that filing a feedback report would be inappropriate because I really can't be sure what I did, and with which versions of MacOS & Xcode.

  • Why can't you write a small amount of code to dump all the records in your container and attach them to a feedback report? A single CKFetchRecordZoneChangesOperation would do the trick and you could just log the record contents as the operation processes them. That would tell you what CDMR records have invalid / old keys in them vs. your existing model.

  • There are hundreds of main records, each referencing hundreds of other entities' records, each referencing dozens of other entities' records- so no to a dump. Similarly, deleting a record associated with invalid/old keys has cascading effects - so no to that approach also. I fixed the problem using the approach described below.

Add a Comment

Fixed the problem by these steps:

  1. Created a backup of the MacMini's persistent store, using code for a temporary persistent store coordinator.migratePersistentStore.
  2. Also created CSV file backups of all objects of all entities (as a fall-back if the migrate didn't work correctly).
  3. Deleted all objects of all Entities in the MacMini persistent store.
  4. Waited for deletions (Step 3) to be propagated to the CloudKit container.
  5. Checked the CloudKit container (Web Console) and manually deleted any Entity Objects not deleted by Steps 3 and 4 - there were quite a few.
  6. Restored the MacMini persistent store by code for:

a persistentStoreCoordinator.destroyPersistentStore of the original MacMini store

b persistentStoreCoordinator.addPersistentStore based on the backup store

c persistentStoreCoordinator.migratePersistentStore from the backup to the "new" store (step 6 b)

  1. Waited for the "new store" Entity Objects to be propagated to the CloudKit container.
  2. Checked the CloudKit container (Web Console) - all good
  3. Checked that additions/deletions worked correctly whether on the MacMini or via The CloudKit Console - all good.

Somewhat of a pain, but necessary given the amount of data and its complexity. An advantage is that I now have backup and restore functions in the file menu of the MacMini App.

Regards, Michaela