0

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:

  1. The app on launch uses STORAGE 1 (local) as expected and, not finding the DEFAULT object of type MyManagedObjectsCategory, it creates a new one.

  2. 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

cdf1982
  • 764
  • 1
  • 18
  • 34
  • 1
    When using CodeData with ICloud it's gonna be difficult to avoid out of sync data. At my company we made one app that way and I assure you we will never do it again. The opacity of the system makes it very difficult to fix the out of sync data. Apple support on that particular problem has been very ... silent. My advice to you: Do not use CoreData with ICloud and most of all do not believe people who tell you it works wonders cos it doesn't. – BotMaster Apr 14 '15 at 12:40

1 Answers1

3

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.

Community
  • 1
  • 1
Tom Harrington
  • 69,312
  • 10
  • 146
  • 170
  • 1
    Hi and thank you for your answer, particurarly appreciated since I've spent the time since posting the question with your blog post and Apple's SharedCoreData WWDC's project in front of me: I already have a unique ID for the category entity and, following your post and Apple's deDupe sample code, I'm attempting to do what you suggest. It's a bit hard for me, since I'm actually translating Objective C code to Swift, but before leaving my computer (I'm on the phone now, sorry for typos) I was able to log the duplicates to the console, so I suppose I'm on a good path... You post has been precious – cdf1982 Apr 14 '15 at 16:59
  • ... now let's see if I'm able to accomplish the task (I'm no expert in programming). If I will, I'll let you and everybody know, posting my Swift version of Apple's deDupe method from the pretty old sample project... There are tricky parts with dictionaries, but I'm optimistic. Thanks again! – cdf1982 Apr 14 '15 at 17:01
  • Good morning @TomHarrington, I think I've managed to successfully port the deDupe method of SharedCoreData from Objective C to Swift. Before posting my code here, I have one more question, if I may: I call my deDeduplica method in `persistentStoreDidImportUbiquitousContentChanges`, after merging changes, but this means that at the moment my deDuplicate method is called every time an object is added, updated or deleted and, of course, this is not efficient. I see you wrote in your blog post... – cdf1982 Apr 15 '15 at 09:24
  • _"The incoming change notification has separate lists of inserted, updated, and deleted objects. If you've scanned for duplicates in the past, you know there won't be any new duplicates unless at least one new object has been inserted. Skip duplicate detection when only updates and/or deletions arrive."_ Sadly, I have to admit that I have no idea how to check the incoming change notification, so I don't know how to skip the call to deDuplicate() when there are only updates & deletions. Can I ask you to maybe point me in the right direction, or if you have a snipplet of code as example? Thanks! – cdf1982 Apr 15 '15 at 09:27
  • The `userInfo` dictionary for that notification has keys named `NSInsertedObjectsKey `, `NSUpdatedObjectsKey `, and `NSDeletedObjectsKey` which can be used to examine the changes which have occurred. You only care about `NSInsertedObjectsKey` in this case. – Tom Harrington Apr 15 '15 at 15:02
  • Thanks again for your help, I'll look into it as soon as I'll be able to handle properly the duplicates deletion... Now, I'm facing the fact that the deDupe method is called in `persistentStoreDidImportUbiquitousContentChanges:` as soon as the first change is imported, so the MyManagedObjectsCategory duplicated is deleted right away, and when a few seconds later other MyNSManagedObjects that have that MyManagedObjectsCategory as property are downloaded, they are put in an unsorted section of the tableViews, because of course the object in their property is now gone... I'm trying to move them.. – cdf1982 Apr 16 '15 at 08:53
  • .. before deleting the project, but I'm having a few difficulties, since I don't know how to check that everything has been imported and, then and only then, delete the duplicate MyManagedObjectsCategory that at that point shouldn't be associated to any MyManagedObject anymore. Chances are I'm out of my league, but let's keep trying! Thanks again, really appreciate your support! – cdf1982 Apr 16 '15 at 08:54