Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Syncing relationships using CoreData and CloudKit

Tags:

TLDR version: How does my app know that a CoreData object on device A, a CoreData object on device B and a CKRecord in CloudKit are all the same record?

Detail version:

I'm working on an app that uses CoreData locally and CloudKit to sync between devices. I don't understand how CoreData relationships and CloudKit references are supposed to work together, once you add in multiple devices.

Some truths as I understand them:

1) When you create a CoreData object it gets assigned an objectID. You cannot assign one at creation yourself or modify one that exists already. From the CoreData relationship field you can directly access the related objects or you can get the objectID of those related objects with a convenience method.

2) When you create a CloudKit record, you can assign it a recordName (basically the CloudKit equivalent of a CoreData objectID) or one is generated by default. A CloudKit reference field contains a string that is the recordName of the associated record.

So here’s how my app works (and doesn’t work)

Device A launches the first time. CoreData instantiates a bunch of default objects, which the user can then edit (or add to/delete) when using the app. When setup is complete and the app determines everything is a go it does a sync with CloudKit. It first looks to pull down any records that already exist (none), and uploads new records to CloudKit using the CoreData objectIDs as the CloudKit recordNames. So the objectIDs on Device A and the CloudKit recordNames are the same. There are multiple relationships between these various objects. So far so good - this part works (modifications in the dashboard update relationships in coredata correctly and vice versa).

Things now go off the rails as Device B launches for the first time. CoreData instantiates the same bunch of default objects (with unique objectIDs), which the user can then modify or add/delete when using the app. When setup is complete and everything is a go, it does a sync with CloudKit. It pulls down all existing records from CloudKit and updates local records that match. Matching will never happen based on recordName <-> objectIDs because Device B has unique objectIDs. This is where my understanding is falling short.

My current attempted solution

My solution currently is to add a custom ckID field to both CoreData entities and CloudKit records. On the first sync on each device it tries to sync these field appropriately. If there are records in CloudKit it goes through those and tries to match existing CoreData objects to those CloudKit records based on the custom fields and entity type - so for example the 'name' field of an entity. When it finds a match it updates the ckID field locally. If none is found it makes a new CoreData object. This ckID field is used any time the app looks for a CoreData relationship or creates a CKReference. Overall this feels like I'm doing a lot of work I shouldn't need to.

Thank you in advance for any help!

UPDATE 11/3/17

One week later and overall this sure seems to be the wrong way to go about syncing between devices. My custom ID field is getting out of sync causing all kinds of mayhem.

I continue to believe I'm missing something very basic here. How does a record relate to itself in a different location? If it was possible to modify an existing CoreData objectID (or assign one at creation) this would be easy - what am I missing?!? What I am doing now feels very wrong but I am plowing ahead not knowing what else to try...

like image 665
Brian M Avatar asked Oct 27 '17 00:10

Brian M


1 Answers

As you mentioned, objectIDs will be different on Device A and Device B. If you really wanted to avoid an identifier field in your objects you could keep a lookup on each device to map from recordName to objectID, however if you ever update your Core Data model your objectIDs will change, so this approach would be very inflexible.

Your approach to use a ckID field is correct, and yes, you actually need to do this much work. CloudKit is just a transport layer, achieving synchronization is a beast of its own.

I recommend looking at existing solutions such as Ensembles, Seam, or SyncKit (of which I'm the author)

like image 152
Manuel Avatar answered Sep 25 '22 02:09

Manuel