60

So, it finally happened. The worst case scenario for any independent iPhone developer occurred. Several users are reporting complete data loss after upgrading my app. iCloud Core Data sync is not working. My users are using this app partially to run their businesses. This is a truly catastrophic failure.

The only iCloud related thing I changed was to add the key-value store to iCloud. The core data code remained exactly the same, same model version (no migration) etc.

In my tests everything worked beautifully! But to my dismay, users reported that their data was not there anymore when they opened the updated app.

What could be the reason for this?

  • The persistent store URL (an ubiquitous URL) should not have changed.
  • Merge conflicts are also unlikely, as this problem should have arisen before the update.
  • Some interference with the new ubiquitous key-value store perhaps?
    (I have pretty much ruled this out.)

Below please find the code for my managed object model and persistent store. Let me know if you need anything else to assess the problem.

- (NSManagedObjectContext *)managedObjectContext {

    if (managedObjectContext_ != nil) {
        return managedObjectContext_;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        managedObjectContext_ = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [managedObjectContext_ performBlockAndWait:^{
            [managedObjectContext_ setPersistentStoreCoordinator:coordinator];
            if (useICloud) {
                [managedObjectContext_ setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
                [[NSNotificationCenter defaultCenter] addObserver:self
           selector:@selector(mergeiCloud:)
           name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
           object:coordinator];
            }
        }];
    }
    return managedObjectContext_;
}

and

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (persistentStoreCoordinator_ != nil) {
        return persistentStoreCoordinator_;
    }

    NSMutableDictionary *options = [NSMutableDictionary dictionary];
    NSURL *storeURL = [[self applicationDocumentsDirectory] 
            URLByAppendingPathComponent:@"SalesCalls.sqlite"];

    [options setObject:[NSNumber numberWithBool:YES] 
                  forKey:NSMigratePersistentStoresAutomaticallyOption];
    [options setObject:[NSNumber numberWithBool:YES] 
                  forKey:NSInferMappingModelAutomaticallyOption];

    if (useICloud) {  // this is an internal flag set to YES
        NSURL *iCloudURL = [[NSFileManager defaultManager] 
                               URLForUbiquityContainerIdentifier:nil];

        if (nil!=iCloudURL) {
            NSString *cloudData = [[iCloudURL path] 
                       stringByAppendingPathComponent:@"data"];
            iCloudURL = [NSURL fileURLWithPath:cloudData];      

            [options setObject:@"at.topofmind.salesplus.store" 
                        forKey:NSPersistentStoreUbiquitousContentNameKey];
            [options setObject:iCloudURL 
                        forKey:NSPersistentStoreUbiquitousContentURLKey];

            NSURL *nosyncDir = [iCloudURL 
                        URLByAppendingPathComponent:@"data.nosync"];
            [[NSFileManager defaultManager] createDirectoryAtURL:nosyncDir 
                        withIntermediateDirectories:YES 
                        attributes:nil error:nil];

            storeURL = [nosyncDir 
                        URLByAppendingPathComponent:@"SalesCalls.sqlite"];
        }
    }

    NSError *error = nil;
    persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] 
             initWithManagedObjectModel:[self managedObjectModel]];
    if (![persistentStoreCoordinator_ 
                  addPersistentStoreWithType:NSSQLiteStoreType
                  configuration:nil URL:storeURL options:options error:&error]) 
   {
        NSLog(@"Cannot create persistent store coordinator, %@, %@", 
                        error, [error userInfo]);
        abort();
    }    

    return persistentStoreCoordinator_;
}

Comments, opinions, wild guesses etc. (and of course solutions!) are all welcome.

UPDATE:

One of my customers lost all his data and after reinstalling and resetting everything (rebooting device, etc.) he could not sync with iCloud any more. The symbol simply does not show up in the Settings.

Mundi
  • 79,884
  • 17
  • 117
  • 140
  • Wild guess . Are you 100% sure you are attaching to the correct ubiquity container in the iCloud setup? – Warren Burton Jan 23 '13 at 11:38
  • Hi Mundi, just want to say that it is entirely possible this is just buggyness on Apple's behalf. Their cloud stuff has come under flak in the past and my understanding is that their iCloud iOS stuff is quite flaky. http://www.tuaw.com/2012/11/27/apple-needs-to-learn-how-the-internet-works-before-icloud-evapor/ – occulus Jan 23 '13 at 11:44
  • @WarrenBurton - Yes, see the code above. It's all there. I removed the log statements, but I did check the ubiquity URL. BTW, I would not know what would make them "correct" or not, as long as they remain the same. – Mundi Jan 23 '13 at 11:53
  • @occulus Tell me about it... That's why I put "More ... woes" into the title. – Mundi Jan 23 '13 at 11:54
  • Not sure yet, but first, have these users check how much data your app is using in iCloud (via Settings.app). It's likely that the data is all still there but temporarily inaccessible. – Tom Harrington Jan 23 '13 at 22:32
  • Not that it's worth much, but been in the same boat. I gave up on CoreData syncing via iCloud. For me it just hung: no error, no nothing! User goes to load app and hangs.. !! – Rob Jan 27 '13 at 22:07
  • Well, let's hope we can both profit from this question and a viable answer. – Mundi Jan 27 '13 at 22:22
  • *"iCloud Core Data sync is not working”*. Sounds about right to me. I've worked with it a bunch and have yet to find a way to make it work reliably. – Tom Harrington Jan 28 '13 at 04:37
  • 13
    Do yourself a favor and give up on iCloud and Core Data if you can. It's simply not ready, and I have *never* heard of any developer who got the combination to work reliably. If you find one (ideally with open source code), please point them out. (Even in apps where it supposedly works, you can be pretty sure that syncing simply stops after a while.) – mrueg Jan 28 '13 at 12:11
  • I am afraid I cannot give you a solution, but I completely understand you. I had to ship my application without CoreData support, and was suggested to do so by Apple support, because of some serious bug that was causing data loss on iOS 5. And I supposed it's the same with iOS 6. This is the relevant question and answer: http://stackoverflow.com/questions/11042558/error-messages-in-ios-coredata-icloud-after-deleting-and-redeploying-app – Leonardo Jan 28 '13 at 13:56
  • wild guess here, at first sight, your path looks like ICLOUD/at.topofmind.salesplus.store/data/data.nosync/SalesCalls.sqlite and you are persisting via coordinator to ICLOUD/at.topofmind.salesplus.store/data/ I am not sure but I guess it would be better to have a structure like at.topofmind.salesplus.store/data and at.topofmind.salesplus.store/data.nosync. – Deniz Mert Edincik Jan 29 '13 at 01:07
  • @DenizMertEdincik Good catch. I will try see if that makes any difference (how?). But then again, it did work and it should not make any difference in theory... – Mundi Jan 29 '13 at 13:33
  • have you considered removing what you added since it worked before. And store what you are storing in the key-value store in core data instead? – Pascal Belloncle Jan 30 '13 at 23:16
  • It *is* working for 98% of the people. The key value store enables a specific and important requested feature. To store that in core data would require to change the data model - likely a recipe for even greater problems (although not necessarily - I have had several successful migrations). – Mundi Jan 31 '13 at 00:13
  • That's also what I found: for no rhyme or reason: it works for most, and then produces a catastrophic failure for the rest. – Rob Jan 31 '13 at 00:18
  • Wild Guess edge case... if the persistent store doesn't get created because of a out of memory issue... it looks like you're passing an empty managed object context. – kineticfocus Jan 31 '13 at 09:55
  • Good idea. No, that's not it because even the failing users can create new records without problems. Also, I guess that should actually result in a crash rather than a silent data wipe. No crashes were reported. – Mundi Jan 31 '13 at 09:57
  • Wild guess - maybe the 2% were using unlocked phones? Of course you'll never get them to admit it! The code you added is it this? `[options setObject:@"at.topofmind.salesplus.store" forKey:NSPersistentStoreUbiquitousContentNameKey]; [options setObject:iCloudURL forKey:NSPersistentStoreUbiquitousContentURLKey];`. In the future, consider checking first for existing values, and backing them up locally on the device. Also, if your data is not too huge, consider doing a backup from iCloud to some other secure location, with the user's permission, prior to the update. – Yimin Rong Feb 01 '13 at 16:52
  • Good advice, thank you. I have at least one customer who would admit it if it were the case. I will let everyone know once I find out. – Mundi Feb 01 '13 at 17:32
  • @Mundi did you ever figure this out? I'm in the middle of doing a lightweight migration with Core data. My app can be run two ways: local storage or iCloud. With local storage, it all works perfectly. With iCloud, all data is lost when the app updates. I would like to send this update to Apple but am afraid of angering my existing users. Any ideas? – Choppin Broccoli Nov 09 '13 at 06:08

1 Answers1

1

I was facing something similar with few uses ( by your description I assumed you have way more volume than I do, that's might the reason for several cases )

In my case Apple seemed to have removed without earlier notice the 20gb free space in the icloud. I noticed that the 2 uses who are power data usage users had lost of new data from my app (which was to deal with historical stock pricing ) and the others ones we just fine downloading the same data size.

I followed up with those 2, helping them to clean stuff up to let more space in icloud and voila, after downloading the data again it worked fine.

RollRoll
  • 8,133
  • 20
  • 76
  • 135