3 Replies
      Latest reply on Dec 3, 2019 12:20 PM by Macho Man Randy Savage
      crafterm Level 1 Level 1 (0 points)

        Hi There,

         

        I've been experimenting with CKFetchRecordZoneChangesOperation and have noticed that if I save two records that have a reference between them with CKModifyRecordsOperation, the next call to CKFetchRecordZoneChangesOperation invokes the recordChangedBlock callback with the leaf, ie. child node first, and then the referenced, ie. root node last.

         

        This means if both records are new we have a recordChangedBlock being called with a reference to another record, that might not yet be in the "local cache".

         

        Is this correct behaviour, ie. there's no dependency order in the recordChangedBlock callback? Any thoughts on how people have been dealing with this? I've seen one post of people gathering up all the callbacks to recordChangedBlock and then doing multi-loop passes over it to check the types of records and process them in type/relationship order!

         

        Looking forward to hearing if others are experiencing the same behaviour?

         

        Cheers,

         

        Marcus

        • Re: CKFetchRecordZoneChangesOperation and reference order of recordChangedBlock calls?
          MendelK Level 2 Level 2 (90 points)

          Don't rely on ANY order whatsoever when downloading changes. Most of the time, the records that were modified first are downloaded first - but this is just an observation and you should not rely on this behaviour.

          • Re: CKFetchRecordZoneChangesOperation and reference order of recordChangedBlock calls?
            Macho Man Randy Savage Level 3 Level 3 (385 points)

            I'm still writing my CKFetchRecordZoneOperation code...and I'm thinking about this situation. My database has a rule:

             

            Child record requires existence of a parent record. In CloudKit terms, there is a CKReference wiith CKActionDeleteSelf. In my local SQLite database, the child table has a foreign key constraint referencing the parent.

             

            During a CKFetchRecordZoneOperation, if another device creates a series of parent-child records and the current device is picking up the new records and saving it to the local cache, the insert for the parent record needs to be processed before child records. I haven't gotten to extensive testing yet, but if CKFetchRecordZoneOperation makes no guarentee of the order and passes changed child records to the app before the parent record, this could break local database rules and inserts of child records will be rejected by the local database because the parent isn't loaded yet. So say the parent record: Drinks has three children Juice, Milk, and Water. If CKFetchRecordZoneOperation hands back Juice, Milk, and Water before Drinks inserts of the child records will fail on the local database.

             

            As the original poster mentioned, this could be addressed by the client code by sorting changes by record type:

             

             

             [changeOp setRecordChangedBlock:^(CKRecord * _Nonnull record)
                {
                    [changedRecords addObject:record];
                }];

             

            Then later when applying changes sort the array:

                NSArray <ckrecord*>*sortedChangedRecs = [changedRecords sortedArrayUsingComparator:^NSComparisonResult(CKRecord *obj1,
                                                                                                                          CKRecord *obj2)
                {
                    //Process
                    if ([obj1.recordType isEqualToString:@"GroupRecord"])
                    {
                        return NSOrderedAscending;
                    }
                   else
                   {
                         return NSOrderedDescending;
                    }
                  
                }];

             

            But still may leave room for issues if CloudKit sends a client some children but doesn't send the parent over in the same batch, then sorting by record type will fail, because they aren't in the same batch. I can't assume CloudKit makes any guarantees that this stuff won't happen because I cannot find any documentation on this matter.

             

            So it seems my choice is either:

             

            -Allow the local database to insert child records where no parent exists (which is technically in violation of my app's rules, and could lead to orphaned entries in the database), but this would allow for children to be processed before parent records in a CKFetchRecordZoneOperation.

             

            Or

             

            -Attempt to insert into the local database with the child record blindly and if I get a constraint violation error, attempt to handle the error by querying CloudKit for the parent. This doesn't seem ideal because the client could be a few change tokens behind and querying the current database mid CKFetchRecordZoneOperation seems to undermine the purpose of CKFetchRecordZoneOperation (have the server tell you what changed based on the token instead of hard querying the database all the time)...
            ---

             

            Unless Cloudkit is sophisticated enough to pass these changes back in logical order and I'm just being paranoid?