38

I am going to start updating this to help those seeking to use this as reference for their own personal code.

Newest update

  • I'm fairly sure I have found a way to resync devices back together once they have stopped talking to each other. I'm going to update my answer below with all of the details. I thoroughly hope you all find this helpful. It's taken almost 2 months of trial and error to figure this one out. So please reference and share this with others who are having similar issues getting devices to once again talk to each other through iCloud. It took me forever to figure this all out, so I am more than happy to save as many other developers as possible from having to create their own make-shift fixes.

Another addition to help set up correctly

  • I found that after updating an app that has iCloud data associated with the account can cause a crash upon opening it because the iCloud data will attempt to merge immediately into the device (where the device has not yet set up its persistent store). I have now added @property (nonatomic, readwrite) BOOL unlocked; to AppDelegate.h and @synthesize unlocked; to AppDelegate.m. I then changed my - (NSPersistentStoreCoordinator *)persistentStoreCoordinator method as well as my - (void)mergeChangesFrom_iCloud method, both of which will be shown below (in the middle for the persistent store setup and at the bottom for the iCloud merge method). In essence, I am telling the app to prevent iCloud from merging data until the app has set up its persistent store. Otherwise, you will see the app crash due to unreadable faults.

Here is how I am setting up my persistentStoreCoordinator:

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


    // here is where you declare the persistent store is not prepared;
    self.unlocked = NO;

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Maintain_My_Car.sqlite"];

    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];   

    NSPersistentStoreCoordinator *psc = __persistentStoreCoordinator; 

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSDictionary *options = nil;

        NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];

        NSString *coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"data"];

        if (coreDataCloudContent.length != 0) {
            // iCloud enabled;

            cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];
            options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, @"<bundleIdentifier>.store", NSPersistentStoreUbiquitousContentNameKey, cloudURL, NSPersistentStoreUbiquitousContentURLKey, nil];

        } else {

            // iCloud not enabled;
            options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

        }

        NSError *error = nil;

        [psc lock];

        if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {

            NSLog(@"bad things %@ %@", error, [error userInfo]);
            abort();

        }
        [psc unlock];

        // the store is now prepared and ready for iCloud to import data;
        self.unlocked = YES;


        dispatch_async(dispatch_get_main_queue(), ^{

            NSLog(@"iCloud persistent store added");

            [[NSNotificationCenter defaultCenter] postNotificationName:@"RefetchAllDatabaseData" object:self userInfo:nil];

        });
    });

    return __persistentStoreCoordinator;
}

<myAppKey> and <bundleIdentifier> are actual values, of course. I am just masking them for the purpose of sharing this code.

I know that some people are still having troubles with this and may be using this question as reference on how to set up their own iCloud-enabled Core Data applications, so I want to update this whenever I make changes to my personal code, ensuring that all of you can use the code that works for me. In this update, I changed the initial cloudURL from [fileManager URLForUbiquityContainerIdentifier:@"<TeamIdentifier>.<bundleIdentifier>"] to [fileManager URLForUbiquityContainerIdentifier:nil], ensuring that the container information is gathered from the entitlements file.

Additional methods _notificationArray is defined as the following: @property (nonatomice, strong) NSMutableArray *notificationArray; @synthesize notificationArray = _notificationArray;

- (void)mergeChangesFrom_iCloud:(NSNotification *)notification {
    if (self.unlocked) {
        NSManagedObjectContext *moc = [self managedObjectContext];

        if (self.notificationArray.count != 0) {
            for (NSNotification *note in _notificationArray) {
                [moc performBlock:^{
                    [self mergeiCloudChanges:note forContext:moc];
                }];
            }
            [_notificationArray removeAllObjects];
            [moc performBlock:^{
                [self mergeiCloudChanges:notification forContext:moc];
            }];
        } else {
            [moc performBlock:^{
                [self mergeiCloudChanges:notification forContext:moc];
            }];
        }
    } else {
        if (_notificationArray == nil) {
            _notificationArray = [[NSMutableArray alloc] init];
        }
        [_notificationArray addObject:notification];
    }
}

- (void)resetStore {
    [self saveContext];
    __persistentStoreCoordinator = nil;
    __managedObjectContext = nil;
    // reset the managedObjectContext for your program as you would in application:didFinishLaunchingWithOptions:
    myMainView.managedObjectContext = [self managedObjectContext];
    // the example above will rebuild the MOC and PSC for you with the new parameters in mind;
}

Then there is the mergeiCloudChanges:forContext: method:

- (void)mergeiCloudChanges:(NSNotification *)note forContext:(NSManagedObjectContext *)moc {
    // below are a few logs you can run to see what is being done and when;
    NSLog(@"insert %@", [[note userInfo] valueForKey:@"inserted"]);
    NSLog(@"delete %@", [[note userInfo] valueForKey:@"deleted"]);
    NSLog(@"update %@", [[note userInfo] valueForKey:@"updated"]);
    [moc mergeChangesFromContextDidSaveNotification:note];

    NSNotification *refreshNotification = [NSNotification notificationWithName:@"RefreshAllViews" object:self userInfo:[note userInfo]];
    [[NSNotificationCenter defaultCenter] postNotification:refreshNotification];
    // do any additional work here;
}

Initial problem

  • Using iCloud on iOS 5.0.1, I'm occasionally getting errors pertaining to the persistent store. I'm going to continue updating this with new information as I find it through experimenting, but so far the solution I provided is the only way I can get the app working properly again (unfortunately jlstrecker's solution didn't work for me) once I start seeing the error, which is the following:

    -NSPersistentStoreCoordinator addPersistentStoreWithType:configuration:URL:options:error:: CoreData: Ubiquity: Error attempting to read ubiquity root url: file://localhost/private/var/mobile/Library/Mobile%20Documents/./data/. Error: Error Domain=LibrarianErrorDomain Code=1 "The operation couldn’t be completed. (LibrarianErrorDomain error 1 - Unable to initiate item download.)" UserInfo=0x176000 {NSURL=file://localhost/private/var/mobile/Library/Mobile%20Documents/./data/, NSDescription=Unable to initiate item download.}

    For the life of me, I cannot figure out why I'm seeing this all the sudden or how to make it stop. I have deleted the app from both devices, deleted the iCloud data which was previous syncing between them, and deleted any data from backups regarding the apps. I have restarted Xcode, restarted both devices, cleaned the Xcode project, yet nothing has stopped the error from showing up. I've never seen this error before and have had zero luck finding anything online on how to pin it down.

    The app crashes here:

    if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
    
        NSLog(@"bad things %@ %@", error, [error userInfo]);
        abort();
    
    }
    

    The log is never hit, nor is the abort. I just see the error above and the app itself becomes unresponsive. If anyone can help point me in the right direction, I would be very appreciative.

Previous issues/questions

  • This seems to continue even after the update from the beta to the public release of 5.0.1. The last time it happened to me was after changing my managed context data model. Considering I haven't released the app yet, I didn't bother merging a new version of the model. I just deleted and reinstalled the app on my devices, but then it refused to cooperate with the data stored in the iCloud container, by which I mean that I received an error that the store could not download items. I imagine this is due to conflicting data model types, which makes perfect sense. So it seems you just need to get rid of the data within the iCloud container without getting rid of the container. Deleting the iCloud data seems to kill everything off, in essence disabling the container and App ID. Since it seemed simpler, I tried creating a new container as suggested by jlstrecker, but unfortunately, this didn't help at all. So once again, I had to go through the steps I outlined in my answer, which again did the trick. But considering how annoying it is to have to create new App IDs and update provisioning profiles each time, I thought it best to update what I've learned to potentially narrow down the cause and get to a quicker solution.

    Going through iCloud > Storage & Backup > Manage Storage, then deleting the app would appear to be the best solution to empty the data, but doing this seems to corrupt the container, leading to the error above. And after successfully doing this, no matter how many times I delete the app and reinstall it to the device (to make it appear like it's the first time appearing on the device and hopefully recreate the container), I can never get the app to show in the Documents & Data list again. This is somewhat concerning if it means that anyone who deletes data from their iCloud like that means that iCloud will not work for the app ever again. I am only using a development profile on the app so far, so perhaps using a distribution profile might make some difference, but I will have to test that before saying anything for certain.

I hope these new updates help anyone who may be having trouble setting up their store. It has been working great for me so far. I will be sure to update more if I find better fixes or just anything that makes the process more seemless.

justin
  • 5,811
  • 3
  • 29
  • 32
  • I also can reproduce this error by deleting my app's data in "Manage Storage" like you are. One fix appears to be changing the NSPersistentStoreUbiquitousContentURLKey - so I will for example change the suffix from "/data" to "/data2" and it will start to work again. That's no solution of course. – kurtzmarc Nov 14 '11 at 03:38
  • slev, it looks to me like a bug in iCloud. Reported — [rdar://10440734](http://openradar.appspot.com/radar?id=1428407). – jlstrecker Nov 14 '11 at 15:30
  • I am with you on this one, @jlstrecker. It seems like the iCloud container deletes the data from the app along with data necessary to initialize itself. I'm glad to see you reported the issue. Should I do so as well or is one enough? – justin Nov 16 '11 at 17:16
  • Has anyone posted a bug to Apple? Isn't that the proper place for this? – kurtzmarc Nov 18 '11 at 02:33
  • 1
    Also, I seem to be able to "reset" the folder by clearing the iCloud container: [fileManager removeItemAtURL:cloudUrl error:&error] so maybe that's a good "master reset" option when iCloud just won't sync. However, it seems like existing stuff in the DB doesn't sync when you do this - just new stuff? – kurtzmarc Nov 18 '11 at 02:57
  • @jlstrecker you also reported that bug to Apple, did you? Or only Open Radar? – Christian Beer Nov 18 '11 at 08:58
  • @kurtzmarc that sounds like it could be a nice workaround, adding some method later to force the current context to add itself to the iCloud persistent store (which would be tedious if you have a large database). I will be reporting this bug to Apple now so this won't be necessary in the future – justin Nov 18 '11 at 20:09
  • I am running into the same problem. Deleting iCloud app store via iCloud->Storage & Backup ... preference stops data (new stuff) from synching and subsequently deleting reinstalling the app leads to hanging on addPersistentStoreWithType:... invocation. – Roman Kishchenko Nov 19 '11 at 16:39
  • My bug report to Apple was closed as a duplicate. – jlstrecker Nov 27 '11 at 18:38
  • @jlstrecker that's odd. Hopefully that means they're looking into the issue, but I still haven't heard anything back on the report I submitted. Hopefully I hear something soon so I can update everyone on how to fix it, or at least to expect a fix in the next iOS update – justin Nov 28 '11 at 02:38
  • I am still have the same issues with the new beta, along with everyone else on the developer forums. The general consensus is that Core Data syncing is still not fixed. Maybe in the next beta? – kurtzmarc Dec 15 '11 at 01:47
  • @kurtzmarc, that is odd. It seemed to work for me just fine. Removing my iCloud storage (via the Settings panel) and restarting the app led to a new iCloud storage folder (with all the previous data still present). But there were no crashes or hangs. What exactly is happening with yours? I'd be happy to play around with it. The more people looking at it, the better. – justin Dec 15 '11 at 16:52
  • I have migrated my Core Data stack to mirror the one in the Core Data Recipe for iCloud syncing - pretty basic. I then updated my device to the latest beta, deleted the app, deleted the iCloud data for the app, reinstalled and it won't sync unless I change the ubiquity folder name and then only new data. Same issues as before, and people on the Core Data Recipes developer forum thread are also reporting the issues still exist. – kurtzmarc Dec 17 '11 at 23:14
  • @kurtzmarc, I'll go look at that. It doesn't seem to be a problem for me anymore if I go through that same process, but I am most likely setting up my storage differently. So I'll take a look at the Core Data Recipes to see what differences exist between their code and mine. I'll be sure to update my answer once/if I find anything useful. – justin Dec 21 '11 at 00:30
  • slev - I think I got it working! One thing that seems to be under-discussed is how to use migratePersistentStore to migrate an existing store to iCloud (and back to local again). I found that you need to pass a physical location for your "new" iCloud database in toURL - don't pass the transaction log location - and it must be different than the store's current location. Still waiting on a new reference app from Apple, but in the meantime we're at least getting somewhere. Thanks to everyone pitching in! – kurtzmarc Feb 19 '12 at 03:34
  • @kurtzmarc I think I can honestly say you are my hero. I've been slaving over trying to get this to work for the last couple weeks to no avail. I actually just now posted a new question trying to figure out what I'm seeing, but you may have just solved it. Any idea what the code would be to pass the store? – justin Feb 19 '12 at 03:49
  • @kurtzmarc could you explain how you got it to work in code please? `pass a physical location for your "new" iCloud database in toURL` whats `toURL`? – Nicolas S Apr 13 '12 at 06:27
  • @Nicolas - look at my other answer. It has a code sample. But it's really not ready for production. Apple needs to provide a reference applications to establish best-practices. – kurtzmarc Apr 15 '12 at 22:12
  • @kurtzmarc oh great! Ill ask you in that answer then.. – Nicolas S Apr 16 '12 at 18:34
  • Thanks so so so so so so so so so so so so so so so SOoOOOoooooo much!!!!!!!!!!!!!! you saved me!! – Bright Lee Jul 09 '12 at 00:08
  • I don't know how to thanks. what's your app? :) – Bright Lee Jul 09 '12 at 00:09
  • one more thing I just want to know is how to force app to refresh if iCloud was updated? (for example, when user back to app from sleep-mode) – Bright Lee Jul 09 '12 at 00:10
  • @BrightLee, I'm glad I could help you out with this. As for forcing the app to check for iCloud updates, it should check by itself within a few seconds to a few minutes at most. If you truly want to force the check, however, you can always reload your persistent store coordinator (like in my `resetStore` method above). This is more of a brute force way of going about it, so I will be sure to update this as I find more elegant ways to perform tasks such as that – justin Jul 09 '12 at 15:37
  • Yes, I was also thinking same way. Thanks so much. I'm so happy with this. thanks for sharing your hard work of two month. God bless you. – Bright Lee Jul 09 '12 at 17:23
  • @BrightLee, my pleasure. And as I said before, I'll keep updating this as I figure out new ways to make the code more efficient. Take care in the meantime. Also, I just realized I forgot to answer your question about the app. It's called Maintain My Car. It's not the prettiest app out there, but it definitely does the trick if you need to keep track of vehicle repairs and the like – justin Jul 10 '12 at 02:49
  • Hello, long time no see :) I have another critical issue.. I have faced same situation with you that it doesn't sync since something was wrong. Problem is this. as you know, All of core datas that have stored should be restored even when you delete your app and reinstall. and I see It works well. but since the problem has appeared, I remove my app and reinstall my app, and nothing is restored! It's just empty data. It's really critical issue... because It's highly linked safety and reponsiblity of apps... do you see what's happening here? Thanks – Bright Lee Sep 13 '12 at 04:25
  • do you think is there anyway to avoid it or restore the datas that I can't restore after app reinstallation? – Bright Lee Sep 13 '12 at 04:31
  • do you think it helps if I do resetiCloudSync:YES as much as I can? --;; – Bright Lee Sep 13 '12 at 04:32
  • @BrightLee, the information from iCloud should immediately start to download after an app is reinstalled, provided the iCloud storage file is not removed (which it will not do when you remove the app and you would have to do manually). What I would suggest would be to search for the app's iCloud container. Though unlikely, it's possible that the app tries to create a new folder before realizing it already has one to link into. – justin Sep 13 '12 at 19:49
  • Thanks so much your reply! the thing you just said "Though unlikely, it's possible that the app tries to create a new folder before realizing it already has one to link into." . Is it really possible??!! Is there any solution when that happen? – Bright Lee Sep 13 '12 at 23:07
  • and function you posted " (void)resetiCloudSync:(BOOL)isSource ". May it give any help to solve my case? – Bright Lee Sep 13 '12 at 23:08
  • I've fallen into this pit, with an app that is in the store. Was working fine, all the sudden, attempt to open the cloud PS just hangs, no log, never gets to the exit call. I can't see how anything but turning iCloud off will work at this point. The only other thought was to maybe try and set a timer and wait for the call to open the PS, and after some time, terminate that thread and then call nuke and pave and create a new store.. ? – Rob Oct 05 '12 at 06:28
  • @Rob I'd venture to say that's probably the best solution at the moment. Set up a timer so that if the persistent store hangs for x amount of time, you can set up a local non-iCloud store (so the user can continue with their use) while trying to reconnect on a background thread. If it reaches so many tries with no luck, give the user an option to completely reset the store from scratch. At this point, you could use the `resetiCloudSync:(BOOL)isSource` method I wrote below or something similar to push all the notifications back into the iCloud persistent store – justin Oct 06 '12 at 04:57
  • hello Slev, how your "mergeiCloudChanges" method looks like? I think It was in your text but I can't find it now(why?). – Bright Lee Oct 09 '12 at 05:51
  • Slev, It's me again. What's diffrence between code you wrote here and other's normal coredata+iCLoud tutorial code. I'm not talking about below resetiCloud method but here. – Bright Lee Oct 10 '12 at 04:58
  • @BrightLee the `mergeChangesFrom_iCloud:(NSNotification *)notification` method is in the above question text. The difference between this code and that of normal tutorial code is that this code has a couple safeguards in case there is an issue creating the iCloud persistent store or the store needs to be reset. The main creation methods are based on the stock code provided – justin Oct 11 '12 at 23:28
  • and my question was that where is "mergeiCloudChanges" method which is called several times in mergeChangesFrom_iCloud method. – Bright Lee Oct 16 '12 at 05:10
  • @BrightLee oh, sorry. I understand what you are asking. I will add it in with the primary question above underneath the `mergeChangedFrom_iCloud` method. Sorry about the confusion. – justin Oct 18 '12 at 22:52
  • OK, so I'm coming into this way after the fact, but has anyone created a category with this code so that it is one place? I _think_ I can get the current version of what has been done here, but I think it would be great if, given that it seems to have helped so many, it was put into a category and made available on github or similar. – PKCLsoft Feb 10 '13 at 05:29
  • 1
    @pkclSoft I'll see what I can do to get a test project up on github for this purpose. I've got a few days off work coming up soon and will work on it then. I'll update this thread with a link to the source code when it gets uploaded – justin Mar 03 '13 at 17:27
  • @slev, that would be brilliant. I've had to put icloud aside for now so that I can focus on the primary functions of the app, but it's really important to get that icloud sync going. – PKCLsoft Mar 03 '13 at 22:21

5 Answers5

13

Updated answer to resync your devices Months of tinkering around have led me to figuring out what (I believe) the rooted problem is. The issue has been getting devices to once again talk to each other after they fall out of sync. I can't say for sure what causes this, but my suspicion is that the transaction log becomes corrupted, or (more likely) the container of the log itself is recreated. This would be like device A posting changes to container A and device B doing the same as opposed to both posting to container C, where they can read/write to the logs.

Now that we know the problem, it's a matter of creating a solution. More tinkering led me to the following. I have a method called resetiCloudSync:(BOOL)isSource, which is a modified version of the method above in my original question.

- (void)resetiCloudSync:(BOOL)isSource {
    NSLog(@"reset sync source %d", isSource);
    NSManagedObjectContext *moc = self.managedObjectContext;

    if (isSource) {
        // remove data from app's cloud account, then repopulate with copy of existing data;

        // find your log transaction container;
        NSURL *cloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
        NSString *coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"store"];
        cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];
        NSError *error = nil;

        // remove the old log transaction container and it's logs;
        [[NSFileManager defaultManager] removeItemAtURL:cloudURL error:&error];

        // rebuild the container to insert the "new" data into;
        if ([[NSFileManager defaultManager] createFileAtPath:coreDataCloudContent contents:nil attributes:nil]) {

            // this will differ for everyone else. here i set up an array that stores the core data objects that are to-many relationships; 
            NSArray *keyArray = [NSArray arrayWithObjects:@"addedFields", @"mileages", @"parts", @"repairEvents", nil];

            // create a request to temporarily store the objects you need to replicate;
            // my heirarchy starts with vehicles as parent entities with many attributes and relationships (both to-one and to-many);
            // as this format is a mix of just about everything, it works great for example purposes;
            NSFetchRequest *request = [[NSFetchRequest alloc] init];
            NSEntityDescription *entity = [NSEntityDescription entityForName:@"Vehicle" inManagedObjectContext:moc];
            [request setEntity:entity];
            NSError *error = nil;
            NSArray *vehicles = [moc executeFetchRequest:request error:&error];

            for (NSManagedObject *object in vehicles) {
                NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:object.entity.name inManagedObjectContext:moc];
                // check regular values;
                for (NSString *key in object.entity.attributesByName.allKeys) {
                    [newObject setValue:[object valueForKey:key] forKey:key];
                }

                // check relationships;
                NSMutableSet *relSet = [[NSMutableSet alloc] init];
                for (NSString *key in object.entity.relationshipsByName.allKeys) {
                    [relSet removeAllObjects];

                    // check to see relationship exists;
                    if ([object valueForKey:key] != nil) {

                        // check to see if relationship is to-many;
                        if ([keyArray containsObject:key]) {
                            for (NSManagedObject *toManyObject in [object valueForKey:key]) {
                                [relSet addObject:toManyObject];
                            }
                        } else {
                            [relSet addObject:[object valueForKey:key]];
                        }

                        // cycle through objects;
                        for (NSManagedObject *subObject in relSet) {
                            NSManagedObject *newSubObject = [NSEntityDescription insertNewObjectForEntityForName:subObject.entity.name inManagedObjectContext:moc];
                            // check sub values;
                            for (NSString *subKey in subObject.entity.attributesByName.allKeys) {
                                NSLog(@"subkey %@", subKey);
                                [newSubObject setValue:[subObject valueForKey:subKey] forKey:subKey];
                            }
                            // check sub relationships;
                            for (NSString *subRel in subObject.entity.relationshipsByName.allKeys) {
                                NSLog(@"sub relationship %@", subRel);
                                // set up any additional checks if necessary;
                                [newSubObject setValue:newObject forKey:subRel];
                            }
                        }
                    }
                }   
                [moc deleteObject:object];
            }
            [self resetStore];
        }
    } else {
        // here we remove all data from the current device to populate with data pushed to cloud from other device;
        for (NSManagedObject *object in moc.registeredObjects) {
            [moc deleteObject:object];
        }
    }
    [[[UIAlertView alloc] initWithTitle:@"Sync has been reset" message:nil delegate:nil cancelButtonTitle:@"Dismiss" otherButtonTitles:nil] show];
}

In this code, I have two distinct paths to take. One is for devices which are not in sync and need to have data imported from the source device. All that path does is clear the memory to prepare it for the data that is supposed to be in it.

The other (isSource = YES) path, does a number of things. In general, it removes the corrupted container. It then creates a new container (for the logs to have a place to reside). Finally, it searches through the parent entities and copies them. What this does is repopulate the transaction log container with the information that is supposed to be there. Then you need to remove the original entities so you don't have duplicates. Finally, reset the persistent store to "refresh" the app's core data and update all the views and fetchedResultsControllers.

I can attest that this works wonderfully. I've cleared the data from devices (isSource = NO) who have not talked to the primary device (where the data is held) for months. I then pushed the data from the primary device and delightfully watched as ALL my data appeared within seconds.

Again, please feel free to reference and share this to any and all who have had problems syncing with iCloud.

Answer to original question, which is no longer affected after iOS 5.1 came out, which fixed the crash after removing your app's iCloud storage in your Settings

After many many many hours of trying anything and everything to get this sorted out, I tried creating a new App ID, updated the app's associated provisioning profile, changed around the iCloud container fields to match the new profile, and everything works again. I still have no idea why this happened, but it seems like the iCloud storage associated with that App ID got corrupted?

So bottom line is if this happens to anyone else, follow these steps and you should be good:

  1. Create a new App ID in the Provisioning Portal.
  2. Find the provisioning profile associated with the app. Click Edit->Modify, then change the App ID to the one you just created.
  3. Submit the change, then replace the existing profile in Xcode with the one you just created.
  4. Change all instances of <bundleIdentifier> to fit the new App ID (these would be in your main app Summary page, the entitlements for iCloud Containers and iCloud Key-Value Store, and in your AppDelegate file where you are creating the persistent store, like in my code above).
  5. Restart Xcode since you changed information regarding provisioning profiles (it will complain otherwise and refuse to run on the device).
  6. Ensure that the new profile is on the devices you wish to install the app on, then build and run. Everything should work just fine at this point.
justin
  • 5,811
  • 3
  • 29
  • 32
  • Important question is that when we should use this? when we see they looks like not taking each other? or often? – Bright Lee Sep 14 '12 at 00:38
  • Yes, this `resetiCloudSync:(BOOL)isSource` method should be used when the devices appear to have stopped talking to each other. It basically removes the iCloud notifications from their container, then fills the container with the entirity of the source's database. This ensures all the data is sent to the other device. In use, this method seems to work almost every time, though I've heard from a few customers that their devices still don't talk, so I am continuing to look deeper into their individual problem. – justin Sep 14 '12 at 23:31
  • I'm really curious why only few people(like you) does talk about it. It might cause many problem on many apps! even though create new app ID, It happens again someday. but what you have done is great! – Bright Lee Sep 24 '12 at 02:38
  • just want to know clearly how to use it. so once the devices seems stop talking each other, what we should do is running "[delegate resetiCloudSync:YES];" at the main device and running "[delegate resetiCloudSync:NO];" at the sub device? Am I right? – Bright Lee Sep 24 '12 at 02:40
  • @BrightLee I'm not sure why so few people address this. With any luck, there will be some solid methods available later on as the stock code provided in the Apple examples doesn't always work. And you are correct about the main and sub device. The main, which holds the information you wish to push, will send `[delegate resetiCloudSync:YES]` while the devices that you wish to receive the information should use the same, but with `NO` – justin Sep 25 '12 at 15:46
  • Thanks again :) You were curious same thing! haha :) – Bright Lee Sep 25 '12 at 19:37
  • slev.. long time no see. How's it going? This method works so far? I noticed that many people who wants to use core data + iCloud are having trouble because the connection between devices is end up broken. and They found nothing to solve this problem. Apple's recentest sample code neither works. I'm one of them because I wanted to use newest way of core data + iCloud. and I'm stuck like others. so I want to ask if your method still works. – Bright Lee Dec 31 '12 at 03:52
  • @BrightLee this code still does the trick. Though resetting all settings of the devices, as stated by Eclectic DNA, below, can help as well. It forces the iCloud settings to reset. I feel there must be some sort of handshake protocol that could do this on its own that apple might let us take advantage of. In the meantime, I'm working to build some sort of safeguard that acts as a mediary. I'll be sure to update this thread if anything successful comes of it – justin Jan 01 '13 at 01:21
  • then, It still works, doesn't it? .... wow... that's awesome... You don't know how many people are in the horror to set coredata + iCloud... session 227 video and sample code never give help... – Bright Lee Jan 01 '13 at 15:12
  • unforunenatly, even though this trick works.. I think I can't use this... because iCloud connection will be broken one day, and customer will have to do reset... It might be bad experience for customer. and It will happen again.. – Bright Lee Jan 01 '13 at 15:14
  • Actually, I found many people in same issue, and I found one man who changed his program to use UIDocument with iCloud instead core data. and It works like beatifully he said. now I'm wondering if I also do same thing... I'm thinking if I can just put sqlite file on the iCloud sync storage? It will be problem when It becomes big, but do you think It works? – Bright Lee Jan 01 '13 at 15:17
  • @BrightLee I still have issues keeping CoreData iCloud manageable in my apps. It is absolutely wonderful when it works, but the few out there that have issues make it almost frustrating enough to consider removing. This trick, along with resetting, seem to alleviate it for now, but I might hold off until a more permanent fix comes along. If UIDocument is a viable alternative, I might at least consider it. In either case, I will keep trying what I can to get something more permanent working in the case of CoreData iCloud applications – justin Jan 03 '13 at 04:13
  • Thanks again. please check this link and please be one of us! – Bright Lee Jan 04 '13 at 04:58
7

Another clarification: A similar situation happened to me testing devices on iOS 6.0.1 and the 6.1 beta 2.

It is not completely fixed in iOS 5.1 as stated by @Slev. One device would completely freeze for roughly 80 seconds while trying to access the persistent store on iCloud but would never actually access the information stored there.

I believe this is due to a corrupted log file in the device's OS. Deleting the app or iCloud data on the device did nothing to fix the freeze/inability to access the iCloud store.

The one fix I found was to reset all settings (erase all content works too) on the device. settings->general->reset.

Only then could I access the data on iCloud with my app on that device again. I hope this helps anyone else that came here looking for a solution to a very frustrating bug.

Bruno Vieira
  • 3,884
  • 1
  • 23
  • 35
Eclectic DNA
  • 386
  • 2
  • 5
  • Thanks for sharing this. Knowing that using the Reset All Settings option will fix some of the lingering effects could prove quite useful in trying to find a permanent solution – justin Nov 20 '12 at 04:44
3

I got this error while using one device on 5.0.1 beta 1 and one on 5.0.0.

I got rid of the error by changing the name of the iCloud Container. In the iCloud Containers list, the first one has to match your app ID, but you can add additional containers with different names.

(Like slev's solution of changing the app ID, this is only a good solution if the app hasn't been released yet.)

jlstrecker
  • 4,953
  • 3
  • 46
  • 60
  • When deleting the iCloud data (System Preferences -> iCloud -> Manage -> Delete), I'm getting an error. It says it couldn't delete the data (no explanation). When I close and reopen the view the data appears to be gone, but maybe it's not all gone. That could be related. – jlstrecker Nov 10 '11 at 20:07
  • I can't say I tried to delete the data through my computer. I did it all through the devices using Settings > iCloud > Storage & Backup > Manage Storage. I had no issues doing it that way, though that didn't solve anything for me when I tried until I went about updating my provisioning profile with a new App ID (though using a new iCloud Container name is a great idea as well, and simpler at that). I'm in the middle of downloading/installing the official release of 5.0.1 now since it's acting like it's up to date with over-the-air updates. If the problem shows again, I'll be reporting a bug – justin Nov 11 '11 at 02:31
  • I just wanted to note that I finished updating both devices, then installed a newer version of the app I'm writing (which is where this issue destroyed me last time I updated) and I had zero problems. So if it was a bug in the OS, it appears to be fixed. If it doesn't have anything to do with the OS, then I have no clue where or why it occurs. But I'm definitely curious to see if it cooperates for you as well after the update – justin Nov 11 '11 at 03:54
  • I am seeing this issue in 5.0.1 – kurtzmarc Nov 13 '11 at 00:06
  • Same here @kurtzmarc , so I updated my question with new things I've seen and potential causes/solutions. So far, the solution I gave is the only process I've been able to use to successfully get the app functional again – justin Nov 13 '11 at 19:54
  • Also seeing this issue on 5.0.1. Doing [[NSFileManager defaultManager] removeItemAtURL:cloudURL error:&error]; seems to help. But that's not a solution... – Kukosk Nov 19 '11 at 23:19
0

Excuse me Slev, I've got the same problem that you have when you say that the app is crashing because the iCloud data will attempt to merge immediately into the device (where the device has not yet set up its persistent store) but I can't understand how have you solved this.

Your code is :

- (void)mergeChangesFrom_iCloud:(NSNotification *)notification {
    if (self.unlocked) {
       NSManagedObjectContext *moc = [self managedObjectContext];
        [moc performBlock:^{
            [self mergeiCloudChanges:notification forContext:moc];
        }];
    }
}

I haven't tried it yet but looking at this code I wonder what happen to the notifications that arrive when "unlocked" is false. Would you loose them?

Wouldn't it be better to have a while loop that test the "unlocked" property and spend some timespan until the property becomes true?

I hope you will understand my very bad english... :) Thank you
Dave

mastro35
  • 133
  • 9
  • Hi Dave, this is a good point. My assumption was that the notifications will continue to send until they are recognized by the system (just as iCloud will attempt to download an item until it succeed). Setting up a marker that stores the notifications temporarily until the persistent store is unlocked would be a great safeguard. I will attempt to code up a modified version with that now and update my code when it's ready. Thanks for the note – justin May 15 '12 at 02:08
  • Alright, the code is updated. If iCloud does not resend the information that isn't merged into the context, this updated method will account for that, store any potentially missed notifications, and merge them back to the context in the order they came. I have tested this on my phone already to make sure it works and it ran without a hitch – justin May 15 '12 at 02:54
  • Wow slev, I really didn't know if the notification will continue to be sended until they're recongnized... I assumed no, so this is why I asked what happen to notifications that arrive when "unlocked" is false... :) However, thank you for your code solution! Now I have only some problems in my iCloud sync, I think I messed up all the iCloud data deleting data from preference panel... :( – mastro35 May 16 '12 at 10:06
  • 1
    It's my pleasure to help out. I must say I'm not totally sure if the notifications are resent or ignored during the locked state. So again, i'm glad you pointed that out so I could ensure there's no issue regardless. Hopefully you'll be able to get your iCloud data syncing soon. If you continue to have issues, I'd recommend asking a question on here. I know that I, as well as quite a few others, would be more than happy to try and assist you – justin May 18 '12 at 01:28
0

UPDATE:

Everyone should really take a look at the iCloud Core Data session 227 from WWDC 2012. The source code they provide is an excellent starting point for an iCloud based solution. Really take the time to go through what they are doing. You will need to fill in some holes like copying objects from one store to another and de-duping. That being said, I am no longer using the migratePersistentStore approach as described below for moving between local and iCloud stores.


My original answer:

Slev asked me to post some code for migrating a store from a local copy to iCloud and back again. This code is experimental and should not be used in production. It is only provided here as reference for us to share and move forward. Once Apple releases a proper reference application, you should probably consult that for your patterns and practices.

-(void) onChangeiCloudSync
{
    YourAppDelegate* appDelegate = (YourAppDelegate*) [[UIApplication sharedApplication] delegate];
    NSFileManager *fileManager = [NSFileManager defaultManager];

    if ([iCloudUtility iCloudEnabled])
    {
        NSURL *storeUrl = [[appDelegate applicationDocumentsDirectory] URLByAppendingPathComponent:@"YourApp2.sqlite"];
        NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];
        NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"data"];
        cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];

        //  The API to turn on Core Data iCloud support here.
        NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:@"com.yourcompany.yourapp.coredata", NSPersistentStoreUbiquitousContentNameKey, cloudURL, NSPersistentStoreUbiquitousContentURLKey, [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,nil];

        NSPersistentStore* store = [appDelegate.persistentStoreCoordinator.persistentStores objectAtIndex:0];
        NSError* error;
        if (![appDelegate.persistentStoreCoordinator migratePersistentStore:store toURL:storeUrl options:options withType:NSSQLiteStoreType error:&error])
        {
            NSLog(@"Error migrating data: %@, %@", error, [error userInfo]);
            //abort();
        }
        [fileManager removeItemAtURL:[[appDelegate applicationDocumentsDirectory] URLByAppendingPathComponent:@"YourApp.sqlite"] error:nil];
        [appDelegate resetStore];
    }
    else
    {
        NSURL *storeUrl = [[appDelegate applicationDocumentsDirectory] URLByAppendingPathComponent:@"YourApp.sqlite"];

        //  The API to turn on Core Data iCloud support here.
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                                 [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
                                 nil];

        NSPersistentStore* store = [appDelegate.persistentStoreCoordinator.persistentStores objectAtIndex:0];
        NSError* error;
        if (![appDelegate.persistentStoreCoordinator migratePersistentStore:store toURL:storeUrl options:options withType:NSSQLiteStoreType error:&error])
        {
            NSLog(@"Error migrating data: %@, %@", error, [error userInfo]);
            //abort();
        }
        [fileManager removeItemAtURL:[[appDelegate applicationDocumentsDirectory] URLByAppendingPathComponent:@"YourApp2.sqlite"] error:nil];
        [appDelegate resetStore];
    }
}
kurtzmarc
  • 3,110
  • 1
  • 24
  • 40
  • To make the above closer to a real solution, we should move the sqlite store not to YourApp2.sqlite in the same path as the old store, but to a .nosync folder in your actual ubiquity container. Also, the decision to removeItemAtURL the old store is an open question - great care must be taken to make sure that you don't delete the store prematurely. Also, the way we get the store (by getting objectAtIndex:0) might not be the best way to get that. – kurtzmarc Feb 19 '12 at 21:16
  • It took me forever to realize you had posted this here. This is actually pretty close to what I've personally been trying. At the moment, I've gotten the syncing to work perfectly, but only after deleting my iCloud data for the app in the phone's Settings. After messing around with migration, I started to wonder if it was necessary since we define the NSMigratePersistentStoreAutomaticallyOption as YES. This should do all that for us, I believe. For corrupted storage, we may need to switch some manually, but I think those will be NSMangedObject items as opposed to a store, if that makes sense – justin Mar 31 '12 at 16:52
  • Im about to release a new app to the apple store, so After almost 2 months of posting this answer, @kurtzmarc What would be your choice, to release the new app without icloud and then update it when apple gives us some guide, or release it with all the patches in this question, IF i can get those to work? – Nicolas S Apr 16 '12 at 18:37