I am trying to get iCloud to work with my app, which needs to migrate an existing local store to a ubiquitous store, if the user requests it.
After some nosing around Apple dev forums and elsewhere, I have taken this approach, which is not working consistently. I have actually seen it work, but only after several crashes on Device B (which is populated from iCloud).
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil)
return persistentStoreCoordinator;
NSURL *legacyStoreUrl = [NSURL fileURLWithPath:[[self applicationDocumentsDirectory] stringByAppendingPathComponent:[self activeStoreFilenameUpgraded:NO]]];
NSURL *upgradedStoreUrl = [NSURL fileURLWithPath:[[self applicationDocumentsDirectory] stringByAppendingPathComponent:[self activeStoreFilenameUpgraded:YES]]];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if ((IOS_VERSION_GREATER_THAN_OR_EQUAL_TO(#"5.0")) && (self.iCloudEnabled)) {
NSPersistentStoreCoordinator* psc = persistentStoreCoordinator;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary *cloudOptions = nil;
NSDictionary *localOptions = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:#"<CONTAINER ID>"];
NSString *coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:[NSString stringWithFormat:#"logs%d",[self activeStoreIndex]]];
if ([coreDataCloudContent length] != 0) {
// iCloud is available
cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];
cloudOptions = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
#"MyAppStore", NSPersistentStoreUbiquitousContentNameKey,
cloudURL, NSPersistentStoreUbiquitousContentURLKey,
nil];
} else {
// iCloud is not available
}
NSError *error = nil;
[psc lock];
if(migrateStore) {
migrateStore = NO;
NSPersistentStore *srcPS = [psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:legacyStoreUrl
options:localOptions
error:&error];
if (![psc migratePersistentStore:srcPS
toURL:upgradedStoreUrl
options:cloudOptions
withType:NSSQLiteStoreType
error:&error]) {
NSLog(#"Error migrating data: %#, %# / %# / %#", error, [error userInfo], legacyStoreUrl, upgradedStoreUrl);
abort();
}
}
else {
if (![psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:upgradedStoreUrl
options:(cloudOptions ? cloudOptions : localOptions)
error:&error]) {
NSLog(#"Unresolved iCloud error %#, %#", error, [error userInfo]);
abort();
}
}
[psc unlock];
[[NSNotificationCenter defaultCenter] postNotificationName:#"RefetchAllDatabaseData" object:self userInfo:nil];
} else {
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
NSError *error = nil;
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:legacyStoreUrl options:options error:&error]) {
// error
abort();
}
}
return persistentStoreCoordinator;
}
iCloud Core Data syncing is terribly broken. A more reliable third-party solution is TICoreDataSync; this fork has support for syncing over iCloud, without relying on iCloud's Core Data sync implementation. It simply uses iCloud to sync files and handles the Core Data object syncing on its own.
I gave up on iCloud Core Data syncing and have been building my app with this library instead; so far it's been working great, with none of the issues I saw with Apple's implementation.
I gave up on iCloud integration, for now at least. I don't feel I can offer my users a reliable syncing solution via iCloud given the erratic Core Data performance. I'm holding out hope for iOS 5.1.
Did you make it work?
Try asking here if not - Weird Errors using CoreData with iCloud
Or give us the solution if yes :)
Related
I am adding new Attribute in existing CoreData and i am using default method i.e persistentContainer in app delegate file not persistentStorecordinator. so where to add these option:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
i already add version number in core_data modal. After searching i found that both property is by default true. Is true?
As detailed in Requesting lightweight migration
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:mom];
NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption: #YES,
NSInferMappingModelAutomaticallyOption: #YES};
NSError *error = nil;
if (![psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options error:&error]) {
NSAssert(NO, #"Unable to load persistent store: %#\n%#",
[error localizedDescription], [error userInfo]);
}
Apple's docs give the following example for setting up an (automatic) lightweight migration:
NSError *error = nil;
NSURL *storeURL = <#The URL of a persistent store#>;
NSPersistentStoreCoordinator *psc = <#The coordinator#>;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
BOOL success = [psc addPersistentStoreWithType:<#Store type#>
configuration:<#Configuration or nil#> URL:storeURL
options:options error:&error];
if (!success) {
// Handle the error.
}
However I am using RestKit, which handles the creation of the persistant store behind the scenes. A simplified version of my initialisation code looks like this:
// Initialize RestKit
RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:rootURL];
// Create the object store
objectManager.objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:databaseName
usingSeedDatabaseName:seedDatabaseName
managedObjectModel:nil //Don't need to pass it in. It is infered
delegate:self];
// Create Mappings
...
// Define Relationships
...
// Set Mappings
...
Where should I pass in configuration options given that RestKit creates the persistantStore behind the scenes?
In my understanding RestKit is sitting on top of Core Data. So even when you're using a seeded database and let RestKit assign the object store for the object manager, the sqlite database that's provided by Core Data will be used.
To enable lightweight migration with RestKit, you can use the - (NSPersistentStoreCoordinator *)persistentStoreCoordinator method in the AppDelegate (see this thread)
AppDelegate
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil) return __persistentStoreCoordinator;
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"thenameofyoursqlite.sqlite"];
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Auto migration failed, error %#, %#", error, error.userInfo);
abort();
}
}
You can add these options in RKManagedObjectStore.m, in createPersistentStoreCoordinator method.
For verion 0.10 of RestKit, it is already added, not sure for latest version. But if not already added you can add youself. The final look of the method would be like this.
- (void)createPersistentStoreCoordinator
{
NSAssert(_managedObjectModel, #"Cannot create persistent store coordinator without a managed object model");
NSAssert(!_persistentStoreCoordinator, #"Cannot create persistent store coordinator: one already exists.");
NSURL *storeURL = [NSURL fileURLWithPath:self.pathToStoreFile];
NSError *error;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_managedObjectModel];
// Allow inferred migration from the original version of the application.
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(managedObjectStore:didFailToCreatePersistentStoreCoordinatorWithError:)]) {
[self.delegate managedObjectStore:self didFailToCreatePersistentStoreCoordinatorWithError:error];
} else {
NSAssert(NO, #"Managed object store failed to create persistent store coordinator: %#", error);
}
}
}
I have tested this in my project, by adding 3 new entities and renaming an old entity, and is working perfect without deleting previous app from device.
Hope this helps you.
i am having a bit of a hassle, i created a new version of my context & made it the default one,
after that i changed my code according to the apple docs and it looks like that now:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (persistentStoreCoordinator != nil)
{
return persistentStoreCoordinator;
}
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"database.sqlite"];
NSPersistentStoreCoordinator *psc = persistentStoreCoordinator;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
BOOL success = [psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeURL
options:options error:&error];
if (!success) {
NSLog(#"Unresolved Error");
abort();
}
return persistentStoreCoordinator;
}
but i get an error even though it is 1:1 the same code, in the line BOOL sucess i get an incompatible pointer to integer conversion 'BOOL' with 'NSPersistentstore'
somehow the mapping has worked though and i get the new model in lets say 4 out of 5 times it works, the 5th it throws an error in that line.
any ideas how to fix it?
UPDATE i changed the code a bit and it now looks like that
NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeURL
options:options error:&error];
if (!store) {
NSLog(#"Unresolved Error");
abort();
}
The warning and the error are two separate things.
The warning is that you are treating a pointer as if it were a number. addPersistentStoreWithType:configuration:URL:options:error does not return a BOOL (which is essentially a number), it returns the NSPersistentStore object you are creating. Instead of a boolean success variable, you should be assigning the result to an NSPersistentStore * variable.
In the case of failure, it returns nil and populates the error object. You can obtain more information about the error from this object, such as logging its localizedDescription.
How can I test if the core data database is empty?
I tried:
NSIndexPath *path1 = [NSIndexPath indexPathForRow:0 inSection:0];
NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:path1];
if([[managedObject valueForKey:#"date"] description]!=nil){SOMEFUNCTION}else{SOMEFUNCTION}
Thanks
you have to create a fetchrequest for each entity you use in core data. if the fetchrequest returns without results you don't have objects of this entity stored in your core data.
- (BOOL)coreDataHasEntriesForEntityName:(NSString *)entityName {
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
[request setFetchLimit:1];
NSError *error = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
if (!results) {
LogError(#"Fetch error: %#", error);
abort();
}
if ([results count] == 0) {
return NO;
}
return YES;
}
not perfect I admit but it works
my code:
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:0];
int fufu = [sectionInfo numberOfObjects];
if(fufu!=0){DATABASE IS NOT EMPTY}else{DATABASE IS EMPTY}
if someone know something more efective pls post it
I have these two methods implemented in my appDelegate:
- (NSString *)applicationDocumentsDirectory
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (persistentStoreCoordinator != nil)
return persistentStoreCoordinator;
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"YourApp.sqlite"]];
NSLog(#"storeURL: %#", storeUrl);
NSError *error;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSDictionary * options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error])
{
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be
useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object model
Check the error message to determine what the actual problem was.
*/
}// if
return persistentStoreCoordinator;
}
The storeUrl prints the path to the sqlite database.
If you open this path with a sqlite manager, you're able to see the content of your sql database. I use this SQLite Manager to analyze sqlite databases: SQLite Manager
(You can only use this method on the simulator)
I'm trying to expand my Core Data. So I added a new attribute to my entity and tried using the Automatic Lightweight Migration. But when I'm starting the programm the error Persistent store migration failed missing source managed object model pops up.
Anyone knows what goes wrong?
The relevant part of my AppDelegate.c (in fact I only added NSDictionary *options):
- (NSPersistentStoreCoordinator *) persistentStoreCoordinator {
if (persistentStoreCoordinator) return persistentStoreCoordinator;
NSManagedObjectModel *mom = [self managedObjectModel];
if (!mom) {
NSAssert(NO, #"Managed object model is nil");
NSLog(#"%#:%# No model to generate a store from", [self class], _cmd);
return nil;
}
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *applicationSupportDirectory = [self applicationSupportDirectory];
NSError *error = nil;
if ( ![fileManager fileExistsAtPath:applicationSupportDirectory isDirectory:NULL] ) {
if (![fileManager createDirectoryAtPath:applicationSupportDirectory withIntermediateDirectories:NO attributes:nil error:&error]) {
NSAssert(NO, ([NSString stringWithFormat:#"Failed to create App Support directory %# : %#", applicationSupportDirectory,error]));
NSLog(#"Error creating application support directory at %# : %#",applicationSupportDirectory,error);
return nil;
}
}
NSURL *url = [NSURL fileURLWithPath: [applicationSupportDirectory stringByAppendingPathComponent: #"stats.darx"]];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: mom];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:url
options:options
error:&error]){
[[NSApplication sharedApplication] presentError:error];
[persistentStoreCoordinator release], persistentStoreCoordinator = nil;
return nil;
}
return persistentStoreCoordinator;
}
You need to use a versioned Managed Object Model, containing both versions of your model. The automatic migration still needs to see both the existing and new versions of your model in order to work out what the differences are, and how to handle them.
The error you quote suggests that your app bundle now contains only your new model (the one you want to use), and not the old one (the one you're trying to migrate from). Go back into your version control system and retrieve the old model, then set up a versioned model containing the old model as v1 and the new one as v2.