4

I'm working on a PoC for a new app and I have an issue with two CoreData entities InboxItem and CardSet, each of them having an optional "to one" relationship to each other. When I start the app in the simulator and create those entities everything looks fine in the simulator database and the CloudKit dashboard, i.e. both relations are set. But when I afterwards run the app on my iPhone and wait until those new records are synched and access the relation afterwards there occurs an error stating the the relationship of InboxItem to CardSet was nil. It seems the relation is not synched "downstream" (from CloudKit to CoreData/iCloud to the device) correctly.

Log console shows the following debug output related to the missing relation when CoreData is importing the recent changes via CloudKit:

Importing updated records:
(
    "<CKRecord: 0x7faf0d820ac0; recordType=CD_Card, recordID=89A3FE22-1CC4-447D-BFF0-515E80933551:(com.apple.coredata.cloudkit.zone:__defaultOwner__), recordChangeTag=5, values={\n    \"CD_cardSet_\" = \"5EED257A-8EF8-4749-93EF-CE2882F52C0C\";\n    \"CD_entityName\" = Card;\n    \"CD_id_\" = \"5F2FB4B7-A236-48F5-B2F9-FC2520C29765\";\n    \"CD_language_\" = \"\\Ud83c\\Udde9\\Ud83c\\Uddea\";\n    \"CD_line1_\" = \"\";\n    \"CD_line2_\" = \"\";\n    \"CD_line3_\" = \"\";\n}>",
    "<CKRecord: 0x7faf0d822260; recordType=CD_CardSet, recordID=5EED257A-8EF8-4749-93EF-CE2882F52C0C:(com.apple.coredata.cloudkit.zone:__defaultOwner__), recordChangeTag=4, values={\n    \"CD_entityName\" = CardSet;\n    \"CD_id_\" = \"E4460BCE-CB08-4DDA-8DDC-1BCC23649A91\";\n    \"CD_inboxItem_\" = \"212D86EC-635E-4903-BF51-85B0F36C401B\";\n}>",
    "<CKRecord: 0x7faf0d815830; recordType=CD_InboxItem, recordID=212D86EC-635E-4903-BF51-85B0F36C401B:(com.apple.coredata.cloudkit.zone:__defaultOwner__), recordChangeTag=3, values={\n    \"CD_addedOn_\" = \"2022-02-21 19:44:51 +0000\";\n    \"CD_capturedText_\" = \"Simulator 1\";\n    \"CD_cardSet_\" = \"5EED257A-8EF8-4749-93EF-CE2882F52C0C\";\n    \"CD_entityName\" = InboxItem;\n    \"CD_id_\" = \"68FA5FB8-2752-4E14-AA4C-C82F20215678\";\n}>",
    "<CKRecord: 0x7faf0db06280; recordType=CD_Card, recordID=64324445-8E07-4D20-8212-2491E564530D:(com.apple.coredata.cloudkit.zone:__defaultOwner__), recordChangeTag=6, values={\n    \"CD_cardSet_\" = \"5EED257A-8EF8-4749-93EF-CE2882F52C0C\";\n    \"CD_entityName\" = Card;\n    \"CD_id_\" = \"786AED19-F00C-4694-8978-897FA34E81F9\";\n    \"CD_language_\" = \"\\Ud83c\\Uddec\\Ud83c\\Udde7\";\n    \"CD_line1_\" = \"\";\n    \"CD_line2_\" = \"\";\n    \"CD_line3_\" = \"\";\n}>"
)
Deleted RecordIDs:
{
}
CoreData: debug: CoreData+CloudKit: -[PFCloudKitSerializer updateAttributes:andRelationships:onManagedObject:fromRecord:withRecordMetadata:importContext:error:](1460): Adding mirrored relationship to link for record <CKRecordID: 0x600002e5e3a0; recordName=89A3FE22-1CC4-447D-BFF0-515E80933551, zoneID=com.apple.coredata.cloudkit.zone:__defaultOwner__> related to 5EED257A-8EF8-4749-93EF-CE2882F52C0C by cardSet_
CoreData: debug: CoreData+CloudKit: -[PFCloudKitSerializer updateAttributes:andRelationships:onManagedObject:fromRecord:withRecordMetadata:importContext:error:](1460): Adding mirrored relationship to link for record <CKRecordID: 0x600002e5ebc0; recordName=5EED257A-8EF8-4749-93EF-CE2882F52C0C, zoneID=com.apple.coredata.cloudkit.zone:__defaultOwner__> related to 212D86EC-635E-4903-BF51-85B0F36C401B by inboxItem_
CoreData: debug: CoreData+CloudKit: -[PFCloudKitSerializer updateAttributes:andRelationships:onManagedObject:fromRecord:withRecordMetadata:importContext:error:](1460): Adding mirrored relationship to link for record <CKRecordID: 0x600002e41d00; recordName=212D86EC-635E-4903-BF51-85B0F36C401B, zoneID=com.apple.coredata.cloudkit.zone:__defaultOwner__> related to 5EED257A-8EF8-4749-93EF-CE2882F52C0C by cardSet_
CoreData: debug: CoreData+CloudKit: -[PFCloudKitSerializer updateAttributes:andRelationships:onManagedObject:fromRecord:withRecordMetadata:importContext:error:](1460): Adding mirrored relationship to link for record <CKRecordID: 0x600002e42180; recordName=64324445-8E07-4D20-8212-2491E564530D, zoneID=com.apple.coredata.cloudkit.zone:__defaultOwner__> related to 5EED257A-8EF8-4749-93EF-CE2882F52C0C by cardSet_
CoreData: debug: CoreData+CloudKit: -[NSCloudKitMirroringDelegate managedObjectContextSaved:](2504): <NSCloudKitMirroringDelegate: 0x6000015d0dd0>: Observed context save: <NSPersistentStoreCoordinator: 0x6000005c9c00> - <NSManagedObjectContext: 0x600001584a90>

The inbox item in question is the one with record ID 212D86EC-635E-4903-BF51-85B0F36C401B (the third one of the updated records above). That is, it's obviously imported and the relationship is created as well (see according Adding mirrored relationship to link for record statements).

Update 1: Seems to be a timing issue: NSFetchRequestResult.controllerDidChangeContent (where I currently perform the check that raises the mentioned error) seems to be called too early - i.e. before the CoreData/Coudkit sync has finished - in particular before the relationships have been established. If I wait for 5 seconds before performing the check the error does not occur... In the meantime the debug log shows

CoreData: debug: CoreData+CloudKit: -[PFMirroredOneToManyRelationship updateRelationshipValueUsingImportContext:andManagedObjectContext:error:](440): Linking object with record name <CKRecordID: 0x600001a8a9e0; recordName=212D86EC-635E-4903-BF51-85B0F36C401B, zoneID=com.apple.coredata.cloudkit.zone:__defaultOwner__> to <CKRecordID: 0x600001a8a740; recordName=5EED257A-8EF8-4749-93EF-CE2882F52C0C, zoneID=com.apple.coredata.cloudkit.zone:__defaultOwner__> via cardSet_

Any ideas how to avoid this? When I first started working on this (a couple of month ago) it worked out. I did not change anything since then. Maybe the behavior changed with the introduction of iOS 15?

Update 2: The issue does not occur when running the simulator with iOS 14.5, so the behavior has been introduced somewhere between iOS 15.0 and iOS 15.2 (I'll check this in more detail later). Question is, is it intended behavior or not? From my point of view entities and relations are now updated in separate steps (context is saved two times) which leads to an inconsistent state in the meantime. Anyone else experiencing such issues?

Update 3: Issue occurs from iOS 15.0.

Sebastian
  • 335
  • 2
  • 10

1 Answers1

0

I am facing the same issue. It's quite annoying since you cannot rely on the integrity of your data anymore. I had a lot of crashes caused by this, since my relationships should never be nil and that's what I assume in code.

From my observation, the issue seems to occur only during large CloudKit imports, mostly when installing the app on a new device and downloading all records stored in CloudKit (in my case roughly 3000 records). In this case, the import process seems to call save() on the viewContext multiple times during the import (maybe so it doesn't have to start over?). This, of course, triggers any FetchedResultsController, but not all relationships are setup properly.

Long story short, my workaround for now is to check the integrity of my data manually when I get new data from my FetchController, e.g. something like this:

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    guard let newObjects = fetchController.fetchedObjects as? [MyCoreDataObject] else {
        return
    }

    let validObjects = newObjects.filter { $0.relationship != nil }

    // Use validObjects
}

When I have some time, I will try to prepare an example project to illustrate the issue and make a ticket with an Apple Engineer to see if this is really intended behaviour or a bug.

BlackWolf
  • 5,239
  • 5
  • 33
  • 60
  • 1
    The issues occurs as well, when dealing with a couple of records only. I approached the developer support at Apple and got the following answer yesterday: "This behavior is by design; your application will need to tolerate nil values for relationships between objects. There's no way to guarantee that related objects will sync together." - I asked back if this behavior changed with iOS 15 because the issue does not occur with iOS 14.x. – Sebastian Mar 18 '22 at 11:04
  • I am also seeing this now, and as you point out, it happens on setting up a new device which has a lot of new data to import. – Michael Rowe Apr 16 '22 at 19:12