Reputation: 813
I have a Swift 1.2 app that uses Core Data with iCloud sync.
In the first screen, the user can insert some data to create custom MyNSManagedObjects.
Every MyNSManagedObject must have a specific "group" to which it belongs. This "category" is represented in my data model by another custom NSManagedObject, let's call it MyManagedObjectsCategory.
User can create many MyManagedObjectsCategory objects, but the app also needs a DEFAULT object of type MyManagedObjectsCategory, in case the user doesn't create any different MyManagedObjectsCategory.
Every MyNSManagedObjects can have only 1 MyManagedObjectsCategory, but a MyManagedObjectsCategory can have many MyNSManagedObjects.
When user launches the app, I immediately check if the DEFAULT MyManagedObjectsCategory already exists and if it doesn't I create a DEFAULT MyManagedObjectsCategory object and save it to persistent store. Of course, only the first time the app launches I need to create this object, later I always fetch the object I created on the first launch of the app and use that.
My issue started when I enabled iCloud sync; now, on the first launch of the app, this happens:
The app on launch uses STORAGE 1 (local) as expected and, not finding the DEFAULT object of type MyManagedObjectsCategory, it creates a new one.
If there's network coverage, a few seconds later the app switches to STORAGE 0 (cloud) and saves the DEFAULT MyManagedObjectsCategory object that has just been created; if the device is offline, this doesn't happen immediately, but of course it will occur when network connection becomes available later.
When I launch the app for the first time on a different device, the point 1 and 2 above happen again: since the app starts with storage 1, it doesn't fetch the DEFAULT MyManagedObjectsCategory object and it creates and saves a new one that, a few seconds later or when network is available, is synced to storage 0 as the app switches storage.
As you can imagine, when different devices sync I find myself with multiple DEFAULT objects and, since I'm new to Core Data, I have no idea how to manage this issue.
On one hand, I need the DEFAULT object immediately available when the app launches, so I can't wait the switch to storage 0 (also, because I don't know if the user has network connection, so the storage switch might happen much later); on the other hand, the purpose of the DEFAULT object is to be one, and always the same, on every device.
I understand that, even if every DEFAULT object has matching properties (the object has a name and a myID String property) being created in the exact same way, Core Data creates a unique ID for every managed object and, since the ID doesn't match between the DEFAULT objects created on different devices in different moments, it doesn't merge them in a single DEFAULT object.
So, my questions:
Is there a way to force this merge of the DEFAULT objects into a single one, if certain properties are exactly the same? Is so, how? I suppose I could do it when the app launches, since the duplication of the DEFAULT object would only happen when a new device is added to iCloud.
Is there a completely different way to handle this issue that I'm missing?
I've spent the last 2 months working on this app, but I can't ship something that duplicates objects when syncing, and I have no idea how to fix it, so any help would really, really be appreciated.
Thanks, @cdf1982
Upvotes: 0
Views: 751
Reputation: 70976
This is fundamental to using iCloud, or really any sync mechanism. If your app creates the same instance on multiple devices, and can't sit around waiting to see if it already exists from a different device, then you'll get duplicates.
The only way to handle this is to let the duplicates happen and then clean them up. With iCloud, you do the cleanup when you receive NSPersistentStoreDidImportUbiquitousContentChangesNotification
, indicating that new incoming data is available. The basic scheme is to do a fetch that finds all duplicates and then handles them according to your app's needs (merging/deleting/whatever). I described this in a previous answer and in some detail in a blog post.
You'll make this much, much easier on yourself if your category entity has an attribute that stores a unique ID, and you ensure that you always use the same unique ID value for your default category instance. Then you can simplify the de-duplication by fetching only objects that match the known unique ID value.
Upvotes: 3