13 Replies
      Latest reply on Nov 19, 2019 9:53 AM by JimmyCricket
      Macho Man Randy Savage Level 3 Level 3 (385 points)

        When I run my app and do a CKModifyRecordsOperation to save a record that already exists on the server (with no changetag) to test merging I do indeed get a CKErrorServerRecordChanged.

         

        There is no values in the user info dictionary of this error for CKRecordChangedErrorClientRecordKey or CKRecordChangedErrorServerRecordKey. Instead there is an NSUnderlying error which prints out:

         

        server message = "SaveSemantics is failIfExists, existing record has chaining, new record does not"

         

        So, I was able to track down the cause to hit this condition. The server record is a parent to an existing record. I'm not sure why being the parent would prevent the normal merging flow. In order to "merge" with the server record, I have to create a query operation and pull it down, instead of just getting the record from the user info.


        I was just using parents for potentially future use with sharing (currently not used in my app). But I think i'll just remove parent references because they seem to cause too many problems.

         

        FB7440624

        • Re: Getting CKErrorServerRecordChanged error with no server record in userInfo. Instead there's an underlying error "SaveSemantics is failIfExists, existing record has chaining, new record does not"
          PBK Level 7 Level 7 (3,365 points)

          Could the problem be that the form of the record you are trying to save (i.e. "just getting the record from the user info") does not have a link to that child record but the record that is on the server does have that link.  Therefore there is an inherent conflict - should the new saved record (the one you are trying to save) not have that link or should it retain that link?  The system has no idea.  But it does recognize that its current version of the record has information in it that the new version you are trying to save never knew about.  As you mentioned, one solution is to start with a query of the current record, modify that and save it.  That way you can only conflict with changes that were since the query response, a fraction of a second before the save.  In your case,  by using "the record from the user info" you may be openning yourself to perceived conflicts over the life of the record.

            • Re: Getting CKErrorServerRecordChanged error with no server record in userInfo. Instead there's an underlying error "SaveSemantics is failIfExists, existing record has chaining, new record does not"
              Macho Man Randy Savage Level 3 Level 3 (385 points)

              Yes there is a conflict. The record on the server is the parent of another record. That relationship gets set on the child record (via the parent property).

               

              So say, I have a parent record Drinks. Under drinks child records are Milk and Juice.

               

              Now device number 2 comes along and also creates a record named Drinks but does not have the one on the server yet (obviously). Also device 2 does not create Milk and Juice (or any children for that matter). So device 2 sends its version of Drinks to the server, and I get the server record changed error. How would I handle it? In this case, I'd cache the server records metadata of drinks in my local cache. Later (hopefully soon) I'll get a notification and my fetch zone changes operation will pull down Milk & Juice.

               

              In this case, I'm not sure what the benefit is to not providing the server record in the user info in order to handle the error? Why require me to fetch the server record instead of putting it in the userInfo as always? The way to merge a client record is to take the properties and apply them to the server record, and then re-upload them to Cloudkit. Other objects that have this record as their parent shouldn't be bothered.

               

              Ended up removing parent references because I'm not sharing right now and it doesn't seem worth it, even though my 'workaround' of fetching the record using a query works.

               

              I do still point this record to another record using a CKReference with delete self as the action (so when Drinks gets deleted, Milk and Juice also get deleted automatically). When testing under those conditions, I have been able to get a CKErrorServerRecordChanged error, but I also get the server record in the user info. I'm staying away from the parent property I guess.

                • Re: Getting CKErrorServerRecordChanged error with no server record in userInfo. Instead there's an underlying error "SaveSemantics is failIfExists, existing record has chaining, new record does not"
                  PBK Level 7 Level 7 (3,365 points)

                  TMI. 

                   

                  What you describe is a conflict before you get to 'parent/child relationships'.   Simply...."....device number 2 comes along and also creates a record named Drinks..." - just this alone is a problem.  The current record "Drinks" already exists and could have lots of information in it (not parent/child) that describes years and years of history with milk, juice and maybe even soda.  But this 'device number 2' record is unaware of that history.  What do you want the server to do - lose all that information or ignore the new record?  You need to provide more guidance to handle such conflicts.  But, better, you need to avoid conflicts in the first place by first querying the database for an existing, relevant record and then either create a new one (if none exists) or amend the existing one based upon whether you get to start with a clean slate of drinks or whether you can never have those sugary kinds.   

                  • Re: Getting CKErrorServerRecordChanged error with no server record in userInfo. Instead there's an underlying error "SaveSemantics is failIfExists, existing record has chaining, new record does not"
                    JimmyCricket Level 1 Level 1 (10 points)

                    > Now device number 2 comes along and also creates a record named Drinks but does not have the one on the server yet (obviously). Also device 2 does not create Milk and Juice (or any children for that matter). So device 2 sends its version of Drinks to the server, and I get the server record changed error. How would I handle it? In this case, I'd cache the server records metadata of drinks in my local cache. Later (hopefully soon) I'll get a notification and my fetch zone changes operation will pull down Milk & Juice.

                     

                    My 2 cents is that this error calls for an active & major resolution to ensure the devices are back in sync before waiting on the next fetch to travel down with specific records that need updating. If something happens and you can't get those fetches (CloudKit or device move offline, record changes lost, device termination, etc), it can make resolving your records with a users new changes to the local store a nightmare as they queue on either side. It also can/tends to affect large sets of records.

                     

                    I now assume that the entire local store of relevant objects on device number 2 is entirely out of sync with the cloud records, so instead of trying to resolve it on a scoped single record or record chain basis (Drink, Milk & Juice), I pull down every record in both parent, child record types in the zone and compare, merge them into my current device model with varying degrees of specificity. This helps to avoid records ghosting out on Device 2, while existing on Device 1 and in the cloud. The assumption here being the worst: you'll never get those record changes (changesets have been lost in some way, things have gotten too complex to resolve, or the user is making major conflicts before fetches complete [which sum up the situations when I'd most often experience this error]).

                     

                    From there I split untouched model objects away from those that are new, newer on device, or deleted (aided by a tombstone) and push those changes back into the cloud store. The goal being to resolve all changes in this store in one change cycle. Device 1, 3, 4 receive the updated changes from the Cloud notification or launch fetch changes operation to bring them both to partiy with each other. If one of those devices enters a similar state in the meantime as 2, they can do the same flush, repeating the cycle.

                     

                    Edit, I completely misintpreted your original question: That's (seemingly) bizarre behavior that I'm not getting and can't find information on the net about. When I push a locally created parent record from an object, nil recordChangeTag (which also lacks the CK metadata that exists on its cloud record version and has children records in the cloud) it successfully merges, no fuss. This is one of the first things I tested after I enabled sharing by making sure document titles synced!

                     

                    I definitly do not get a kickback requiring me to pull down the cloud metadata at all.