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;
toAppDelegate.h
and@synthesize unlocked;
toAppDelegate.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