Migrating NSPersistentStore from application sandbox to shared group container - ios

I am trying to move my NSPersistentStore from my app's sandbox to a shared group container so I can access CoreData from my WatchKit extension. I am currently using Core Data with iCloud and want to move the users data to the shared group container. Currently I am creating the NSPersistentStoreCoordinator as follows:
if (__persistentStoreCoordinator != nil) {
return __persistentStoreCoordinator;
}
NSURL *url = [self storeURL]; // app's Documents directory
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:[self iCloudPersistentStoreOptions] error:&error]) {
NSLog(#"Error adding persistent store: %#", [error localizedDescription]);
}
// rest of setup
return __persistentStoreCoordinator;
I've already setup the shared group container in my app target and WatchKit extension target and can get the NSURL for the new store location using - (NSURL *)containerURLForSecurityApplicationGroupIdentifier:(NSString *)groupIdentifier.
How can I check whether I need to migrate or that I've already migrated so I don't attempt to migrate multiple times? Initially I was thinking of something like this, but this doesn't work as the old store URL doesn't exist
if (__persistentStoreCoordinator != nil) {
return __persistentStoreCoordinator;
}
NSURL *newURL = [self newStoreURL]; // shared group container
NSURL *oldURL = [self oldStoreURL]; // app's Documents directory
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:newURL.path] && [fileManager fileExistsAtPath:oldURL.path]) {
NSLog(#"performing migration...");
NSPersistentStore *oldStore = [__persistentStoreCoordinator persistentStoreForURL:oldURL];
NSError *migrateError = nil;
NSPersistentStore *newStore = [__persistentStoreCoordinator migratePersistentStore:oldStore toURL:newURL options:[self iCloudPersistentStoreOptions] withType:NSSQLiteStoreType error:&migrateError];
if (!newStore) {
NSLog(#"Error migrating store: %#", [migrateError localizedDescription]);
}
}
// rest of setup
return __persistentStoreCoordinator;

From what I can tell, it looks like you're pretty much there if the migration logic you posted is working. What it seems you are missing is an else if to handle the case where you don't have a persistent store. The following should handle that case.
if (__persistentStoreCoordinator != nil) {
return __persistentStoreCoordinator;
}
NSURL *newURL = [self newStoreURL]; // shared group container
NSURL *oldURL = [self oldStoreURL]; // app's Documents directory
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:newURL.path] && [fileManager fileExistsAtPath:oldURL.path]) {
NSLog(#"performing migration...");
NSPersistentStore *oldStore = [__persistentStoreCoordinator persistentStoreForURL:oldURL];
NSError *migrateError = nil;
NSPersistentStore *newStore = [__persistentStoreCoordinator migratePersistentStore:oldStore toURL:newURL options:[self iCloudPersistentStoreOptions] withType:NSSQLiteStoreType error:&migrateError];
if (!newStore) {
NSLog(#"Error migrating store: %#", [migrateError localizedDescription]);
}
} else if (![fileManager fileExistsAtPath:newURL.path]) {
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:newURL options:[self iCloudPersistentStoreOptions] error:&error]) {
NSLog(#"Error adding persistent store: %#", [error localizedDescription]);
}
}
// rest of setup
return __persistentStoreCoordinator;

Related

Core Data: The model used to open the store is incompatible with the one used to create the store

My app contains 2 databases:
db1: A read/write database (to store all the user settings)
db2: A readonly database, preloaded in another project (i copied .sqlite, .xcdatamodeld and entities class in the project)
If i initialize Core Data with 2 MOC and 2 PSC (one for each database): everything works fine. But i would like to initialize only 1 MOC/PSC for the two databases. To do this, i wrote the following code:
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *db1ModelURL = [[NSBundle mainBundle] URLForResource:#"db1" withExtension:#"momd"];
NSManagedObjectModel *db1Mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:db1ModelURL];
NSURL *db2ModelURL = [[NSBundle mainBundle] URLForResource:#"db2" withExtension:#"momd"];
NSManagedObjectModel *db2Mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:db2ModelURL];
NSAssert(db1 != nil, #"Error initializing Managed Object Model");
NSAssert(db2 != nil, #"Error initializing Managed Object Model");
_managedObjectModel=[NSManagedObjectModel modelByMergingModels:[NSArray db1Mom,db2Mom, nil]];
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL * db1URL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"db1.sqlite"];
NSURL *db2URL = [[NSBundle mainBundle] URLForResource:#"db2" withExtension:#"sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
_persistentStoreCoordinator = [[self managedObjectContext] persistentStoreCoordinator];
NSMutableDictionary * db2Options=[NSMutableDictionary dictionaryWithObjectsAndKeys:
#YES,NSReadOnlyPersistentStoreOption,
nil];
NSPersistentStore *store = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:#"DB2" URL:db2URL options:db2Options error:&error];
NSAssert(store != nil, #"Error initializing PSC: %#\n%#", [error localizedDescription], [error userInfo]);
NSMutableDictionary * db1Options=[NSMutableDictionary dictionaryWithObjectsAndKeys:
#YES,NSMigratePersistentStoresAutomaticallyOption,
#YES,NSInferMappingModelAutomaticallyOption,
nil];
store = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:#"DB1" URL:db1URL options:db1Options error:&error];
NSAssert(store != nil, #"Error initializing PSC: %#\n%#", [error localizedDescription], [error userInfo]);
return _persistentStoreCoordinator;
}
And when i launch the app, i get the following error on the DB2 database:
The model used to open the store is incompatible with the one used to create the store
I think the problem comes with the call to modelByMergingModels, the resulting model contains the db2Model, but Core Data doesn't recognize it as the base model for this database...
Suggestions?
It is happening because you initially installed the app in your phone using one of the db. Now you have added/changed the DB. So it does not recognise the new DB. Because the app was originally created/installed using a different DB.
Try deleting the app from you phone and install again. The error will go.
Hope this helps. :)
I found a solution reading this article. Even if i don't understand why it works...
- (void)initializeCoreData{
// Initialize models
NSURL *db2ModelURL = [[NSBundle mainBundle] URLForResource:#"villes" withExtension:#"momd"];
NSManagedObjectModel *db2Mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:db2ModelURL];
NSAssert(db2Mom != nil, #"Error initializing Managed Object Model");
NSURL *db1ModelURL = [[NSBundle mainBundle] URLForResource:#"MMAMeteoPro" withExtension:#"momd"];
NSManagedObjectModel *db1Mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:db1ModelURL];
NSAssert(db1Mom != nil, #"Error initializing Managed Object Model");
NSManagedObjectModel * fullModel=[NSManagedObjectModel modelByMergingModels:[NSArray arrayWithObjects:db1Mom,db2Mom, nil]];
// Initialize context
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:fullModel];
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[moc setPersistentStoreCoordinator:psc];
[self setManagedObjectContext:moc];
// Initialize stores
NSURL *db2StoreURL = [[NSBundle mainBundle] URLForResource:#"db2" withExtension:#"sqlite"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *db1StoreURL = [documentsURL URLByAppendingPathComponent:#"db1.sqlite"];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSError *error = nil;
NSPersistentStoreCoordinator *psc = [[self managedObjectContext] persistentStoreCoordinator];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
NSMutableDictionary * db2Options=[NSMutableDictionary dictionaryWithObjectsAndKeys:
#{#"journal_mode":#"DELETE"},NSSQLitePragmasOption,
#YES, NSReadOnlyPersistentStoreOption,
nil];
NSMutableDictionary * db1Options=[NSMutableDictionary dictionaryWithObjectsAndKeys:
#{#"journal_mode":#"DELETE"},NSSQLitePragmasOption,
nil];
NSPersistentStore * tempStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:db2StoreURL options:options error:&error];
NSAssert(error == nil, #"Error initializing PSC: %#\n%#", [error localizedDescription], [error userInfo]);
[psc removePersistentStore:tempStore error:&error];
NSAssert(error == nil, #"Error initializing PSC: %#\n%#", [error localizedDescription], [error userInfo]);
tempStore=[psc addPersistentStoreWithType:NSSQLiteStoreType configuration:#"DB2Conf" URL:villeStoreURL options:db2Options error:&error];
NSAssert(error == nil, #"Error initializing PSC: %#\n%#", [error localizedDescription], [error userInfo]);
tempStore=[psc addPersistentStoreWithType:NSSQLiteStoreType configuration:#"DB1Conf" URL:meteoStoreURL options:db1Options error:&error];
NSAssert(error == nil, #"Error initializing PSC: %#\n%#", [error localizedDescription], [error userInfo]);
});
}
We have to:
Add the ReadOnly persistent store first, with the lightweight migration options on, and no ReadOnly option nor configuration
Remove this persistent store (???)
Add it again, this time without the lightweight migration options, but with the ReadOnly and the good configuration.
Add the Read/Write Persistent Store
If someone can explain me why this configuration works... Cause i really don't get the point here.

Not Able to Migrate Persistent Store Away from iCloud

I am trying to remove the iCloud sync capability from a NSPersistentStore in my app. The migration does not return any errors, however, the data from the iCloud-enabled persistent store disappears after the migration.
The following is my relevant code:
-(void)setUpCoreDataStackWithICloud
{
//Set up the model and context.
self.model = [NSManagedObjectModel mergedModelFromBundles:nil];
self.context = [[NSManagedObjectContext alloc]init];
[self.context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
if ([self isiCloudEnabledOnThisDevice]==YES) {
[self setUpiCloud];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:[NSNumber numberWithBool:YES] forKey:#"lastLaunchOfAppiCloudWasEnabled"];
}else self.psc = [self persistentStoreCoordinatorForUserWithoutiCloudEnabled];
[self.context setPersistentStoreCoordinator:self.psc];
}
-(NSPersistentStoreCoordinator*)persistentStoreCoordinatorForUserThatAlreadyMigratedToiCloud
{
NSLog(#"persistentStoreCoordinatorForUserThatAlreadyMigratedToiCloud called");
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc]initWithManagedObjectModel:self.model];
NSError *error = nil;
NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self newStoreURL]
options:[self iCloudStoreOptions]
error:&error];
if (!store) {
NSLog(#"failed to add store with error %#", [error localizedDescription]);
}else NSLog(#"successfully added store for user that has already migrated to iCloud");
NSDictionary *migrateOptions = #{ NSPersistentStoreRemoveUbiquitousMetadataOption : #YES,
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES};
NSError *migrationError = nil;
NSPersistentStore *migratedStore = [psc migratePersistentStore:store toURL:[self testNewestStoreURL] options:migrateOptions withType:NSSQLiteStoreType error:&migrationError];
if (migrationError) {
NSLog(#"Migration from iCloud enabled persistent store was unsuccessful. %#", migrationError.localizedDescription);
}else NSLog(#"The migration from the iCloud enabled persistent store was successful");
NSLog(#"The migrated store is %#", migratedStore);
return psc;
}
-(NSURL*)testNewestStoreURL{
NSString *path = [self itemArchivePathWithAppendedString:#"test.data"];
NSURL *storeURL = [NSURL fileURLWithPath:path];
return storeURL;
}
-(NSURL*)newStoreURL
{
NSString *path = [self itemArchivePathWithAppendedString:#"cloudstore.data"];
NSURL *storeURL = [NSURL fileURLWithPath:path];
return storeURL;
}
The reason it won't work is because I am also doing a schema migration using a mapping model. iCloud doesn't support this according to the docs. Even though I am trying to get away from iCloud, it does not allow access to the data when there is a schema migration involved.

iOS App mistakenly creating multiple sqlite files for a test case, thus leading to repetitive file downloads

I have implemented core data in my ios app. Now when downloading and saving files to database, if I quit the process in between and then start again it creates new sqlite file everytime. This leads to app taking files from database for first few files, while storing the later files in a seperate database which it doesn't access later. This eventually leads to downloading of later files everytime and creating a new database for it. I am pretty confused on how to fix this. Following is my core data code from AppDelegate class, hoever, I need to run the operation continued in background also , so no fixing there:
#pragma mark - Core Data stack
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
- (NSURL *)applicationDocumentsDirectory {
// The directory the application uses to store the Core Data store file. This code uses a directory named "acme.in.EcoGrid" in the application's documents directory.
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
- (NSManagedObjectModel *)managedObjectModel {
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"EcoGrid" withExtension:#"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it.
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// Create the coordinator and store
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"EcoGrid.sqlite"];
NSError *error = nil;
NSString *failureReason = #"There was an error creating or loading the application's saved data.";
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]) {
// Report any error we got.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSLocalizedDescriptionKey] = #"Failed to initialize the application's saved data";
dict[NSLocalizedFailureReasonErrorKey] = failureReason;
dict[NSUnderlyingErrorKey] = error;
error = [NSError errorWithDomain:#"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
// Replace this 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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext *)managedObjectContext {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}
#pragma mark - Core Data Saving support
- (void)saveContext {
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
NSError *error = nil;
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
-(void)deleteAndRecreateStore{
NSPersistentStore * store = [[self.persistentStoreCoordinator persistentStores] lastObject];
NSError * error;
[self.persistentStoreCoordinator removePersistentStore:store error:&error];
[[NSFileManager defaultManager] removeItemAtURL:[store URL] error:&error];
_managedObjectContext = nil;
_persistentStoreCoordinator = nil;
[self managedObjectContext];
}
Any help here is highly appreciated!
Looks like the problem is resolved by placing an if condition in persistentStore Coordinator method like this:-
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it.
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// Create the coordinator and store
NSFileManager *filemgr = [NSFileManager defaultManager];
if ([filemgr fileExistsAtPath: [[[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"EcoGrid.sqlite"] absoluteString] ] == NO) {
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"EcoGrid.sqlite"];
NSError *error = nil;
NSString *failureReason = #"There was an error creating or loading the application's saved data.";
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]) {
// Report any error we got.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSLocalizedDescriptionKey] = #"Failed to initialize the application's saved data";
dict[NSLocalizedFailureReasonErrorKey] = failureReason;
dict[NSUnderlyingErrorKey] = error;
error = [NSError errorWithDomain:#"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
// Replace this 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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
else{
}
return _persistentStoreCoordinator;
}

SQLite Error 266 on CoreData DB

With my most recent app update, I have started to see very inconsistent SQLite errors when saving my database. These are happening with multiple users, so it is not just the same user crashing repeatedly (though it has happened for the same user multiple times). I am getting error 266, which is SQLITE_IOERR_READ. I haven't found anyone else running into this error, so not sure why I'm getting it.
00:04:18:25 $ -[AppDelegate saveContext] line 328 $ Unresolved error Error Domain=NSCocoaErrorDomain Code=266 "The operation couldn’t be completed. (Cocoa error 266.)" UserInfo=0x1dd141b0 {NSSQLiteErrorDomain=266, NSFilePath=/var/mobile/Applications/[omitted], NSPOSIXErrorDomain=1, NSUnderlyingException=I/O error for database at /var/mobile/Applications/[omitted]. SQLite error code:266, 'not an error' errno:1}, {
* 00:04:18:25 NSFilePath = "/var/mobile/Applications/[omitted].sqlite";
* 00:04:18:25 NSPOSIXErrorDomain = 1;
* 00:04:18:25 NSSQLiteErrorDomain = 266;
* 00:04:18:25 NSUnderlyingException = "I/O error for database at /var/mobile/Applications/[omitted].sqlite. SQLite error code:266, 'not an error' errno:1";
* 00:04:18:25 }
EDIT
Here is the core-data related code (most of it standard boilerplate):
/**
Returns the managed object context for the application.
If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
*/
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}
/**
Returns the managed object model for the application.
If the model doesn't already exist, it is created by merging all of the models found in the application bundle.
*/
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
//managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
// See http://iphonedevelopment.blogspot.com.au/2009/09/core-data-migration-problems.html
NSString *path = [[NSBundle mainBundle] pathForResource:#"modelDB" ofType:#"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
return managedObjectModel;
}
/**
Returns the persistent store coordinator for the application.
If the coordinator doesn't already exist, it is created and the application's store added to it.
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[Utils documentsDirectory] stringByAppendingPathComponent: #"modelDB.sqlite"];
NSURL *storeUrl = [NSURL fileURLWithPath: storePath];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self 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]) {
// Handle the error.
CLS_LOG(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
//Turn on complete file protection (encrypts files when phone is locked using device pin)
NSDictionary *fileAttributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
if(![[NSFileManager defaultManager] setAttributes:fileAttributes ofItemAtPath:storePath error:&error])
{
//handle error
}
return persistentStoreCoordinator;
}
When a user logs out, this is called to remove the model store:
- (NSPersistentStoreCoordinator *)resetPersistentStore
{
NSError *error = nil;
if ([persistentStoreCoordinator persistentStores] == nil)
return [self persistentStoreCoordinator];
[managedObjectContext release];
managedObjectContext = nil;
//If there are many stores, this could be an issue
NSPersistentStore *store = [[persistentStoreCoordinator persistentStores] lastObject];
if (![persistentStoreCoordinator removePersistentStore:store error:&error])
{
CLS_LOG(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
// Delete file
if ([[NSFileManager defaultManager] fileExistsAtPath:store.URL.path]) {
if (![[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error])
{
CLS_LOG(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
// Delete the reference to non-existing store
[persistentStoreCoordinator release];
persistentStoreCoordinator = nil;
NSPersistentStoreCoordinator *r = [self persistentStoreCoordinator];
return r;
}
My app has a single store, so I don't think NSPersistentStore *store = [[persistentStoreCoordinator persistentStores] lastObject]; would cause an issue.
A little late to respond, but I noticed that this error almost always occurred when our app had been pushed to the background.
When creating the persistentStoreCoordinator you may need to set the NSPersistentStoreFileProtectionKey option to NSFileProtectionCompleteUntilFirstUserAuthentication instead of NSFileProtectionComplete.
Note that this slightly elevates the security risk so you may want to consider if this is necessary in your app.
Are you sure that the database is correctly opened / closed at everytime? It can be a problem due to a file open while it was uncorrectly closed

Determining when there are new versions in core data model

Short question:
I want to run a certain code in my app only if my Core Data model has changed (new entities, new properties, etc). How can I determine if the model has changed or not?
Just some pseudo-code:
if (current_model_version != previous_model_version) {
//do some code
} else {
// do some other code
}
I'm guessing I might use versionHashes to do this, or isConfiguration:compatibleWithStoreMetadata:, but I'm not certain how.
Some editing for clarity: 'current' as in 'now' and 'previous' as in 'last time app was launched.'
The answer seems to be isConfiguration:compatibleWithStoreMedia:.
I found some useful information here:
http://mipostel.com/index.php/home/70-core-data-migration-standard-migration-part-2
I set it up this way:
- (BOOL)modelChanged
{
NSError *error;
NSURL * sourceURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"db.sqlite"];
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:sourceURL error:&error];
BOOL isCompatible = [[self managedObjectModel] isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata];
return isCompatible;
}
'self' is my shared data store, not that it necessarily has to go there.
deanWombourne points out that what this really does is determine whether or not the data can be automatically migrated, so it's not exactly the solution to the problem I posed. It does serve my needs in this case.
This is replacement code for - (NSPersistentStoreCoordinator *)persistentStoreCoordinator that you get if you tick the Core Data box when setting up a new project in XCode.
It attempts to open the existing sqlite file (using lightweight migration if necessary). If that fails, it deletes and re-creates the store.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSError *error = nil;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.storeURL options:options error:&error])
{
NSLog(#"Couldn't open the store. error %#, %#", error, [error userInfo]);
[self deleteSqliteFilesForStore:self.storeURL];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
// or [NSException raise ...]
}
else
{
NSLog(#"Store deleted and recreated");
// TEST DATA RE-INITIALIZATION CODE GOES HERE
}
}
else
{
NSLog(#"Existing Store opened successfully");
}
return _persistentStoreCoordinator;
}
- (void)deleteSqliteFilesForStore:(NSURL *)storeURL
{
NSURL *baseURL = [storeURL URLByDeletingPathExtension];
// Delete the associated files as well as the sqlite file
for (NSString *pathExtension in #[#"sqlite",#"sqlite-shm",#"sqlite-wal"])
{
NSURL *componentURL = [baseURL URLByAppendingPathExtension:pathExtension];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[componentURL path]];
if(fileExists)
{
[[NSFileManager defaultManager] removeItemAtPath:[componentURL path] error:nil];
}
}
}

Resources