CoreData: Replace .sqlite causes crash - ios

When I update my app, I'm doing some stuff with my CoreData model on startup and afterwards I replace the .sqlite file the persistent store uses with:
NSArray *stores = [__persistentStoreCoordinator persistentStores];
for(NSPersistentStore *store in stores) {
[__persistentStoreCoordinator removePersistentStore:store error:nil];
[[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:nil];
}
__persistentStoreCoordinator = nil;
[self persistentStoreCoordinator];
__managedObjectContext = nil;
[self managedObjectContext];
Everything works fine, just the way it is suposed to. But when I close the app via the homebutton, it crashes:
[NSPersistentStoreCoordinator retain]: message sent to deallocated instance
I'm using ARC ... actually you could say it doesn't matter, because it crashes when being closed, so you don't notice the crash. But, of course, that's not an option and there has to be a right way to do that!?
Any ideas? Why is there a retain sent to the NSPersistenStoreCoordinator? It has something to do with __persistentStoreCoordinator = nil; but I need to nil it, otherwise it doesn't use the new .sqlite.
Cheers!

Well finaly I found a better (and working) way to replace the .sqlite & storeCoordinator's store, without the need to nil the persistentStoreCoordinator:
NSArray *stores = [__persistentStoreCoordinator persistentStores];
for(NSPersistentStore *store in stores) {
[__persistentStoreCoordinator removePersistentStore:store error:nil];
[[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:nil];
}
NSString *storePath = [[self applicationDocumentsDirectoryString] stringByAppendingPathComponent:#"PictureApp.sqlite"];
NSURL *storeUrl = [NSURL fileURLWithPath:[[self applicationDocumentsDirectoryString] stringByAppendingPathComponent:#"PictureApp.sqlite"]];
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"PictureApp" ofType:#"sqlite"];
if (defaultStorePath) {
[[NSFileManager defaultManager] copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
NSError *error = nil;
[__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error];

Related

Core Data addPersistentStoreWithType return nil, but error is nil too

I'm creating a NSPersistentStore with the code below.
NSPersistentStore * pc = [persistentCoordinator
addPersistentStoreWithType:EncryptedStoreType
configuration:nil
URL:databaseURL
options:options
error:error];
if (*error)
{
NSLog(#"Unable to add persistent store.");
NSLog(#"Error: %#\n%#\n%#", *error, [*error userInfo], [*error localizedDescription]);
}
The value of options is
{
EncryptedStore = SQLite;
EncryptedStoreDatabaseLocation =
"file:///var/mobile/Containers/Data/Application/0C27F628-3FF0-467F-8EF1-5974EBBD3620/Documents/DBEncrypted.sqlite";
EncryptedStorePassphrase = "xxxxxxxxredactedxxxxxxx";
NSInferMappingModelAutomaticallyOption = 1;
NSMigratePersistentStoresAutomaticallyOption = 1;
NSSQLitePragmasOption = {
synchronous = OFF;
};
}
At this point *error is nil and pc is nil too.
According to Apple's documentation if the function returns nil should be an error. Does anyone saw it before?
The EncryptedStoreType is from https://github.com/project-imas/encrypted-core-data
The error only happens if we are migrating the Data Store
EDIT:
Full code of method:
+ (NSPersistentStoreCoordinator *)makeStoreWithOptions:(NSDictionary *)options managedObjectModel:(NSManagedObjectModel *)objModel error:(NSError *__autoreleasing *)error
{
NSPersistentStoreCoordinator * persistentCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:objModel];
// NSString* appSupportDir = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
BOOL backup = YES;
NSURL *databaseURL;
id dburl = [options objectForKey:EncryptedStoreDatabaseLocation];
if(dburl != nil) {
if ([dburl isKindOfClass:[NSString class]]){
databaseURL = [NSURL URLWithString:[options objectForKey:EncryptedStoreDatabaseLocation]];
backup = NO;
}
else if ([dburl isKindOfClass:[NSURL class]]){
databaseURL = dburl;
backup = NO;
}
}
if (backup){
NSString *dbNameKey = (__bridge NSString *)kCFBundleNameKey;
NSString *dbName = NSBundle.mainBundle.infoDictionary[dbNameKey];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *applicationSupportURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
[fileManager createDirectoryAtURL:applicationSupportURL withIntermediateDirectories:NO attributes:nil error:nil];
databaseURL = [applicationSupportURL URLByAppendingPathComponent:[dbName stringByAppendingString:#".sqlite"]];
}
[persistentCoordinator addPersistentStoreWithType:EncryptedStoreType configuration:nil URL:databaseURL
options:options error:error];
if (*error)
{
NSLog(#"Unable to add persistent store.");
NSLog(#"Error: %#\n%#\n%#", *error, [*error userInfo], [*error localizedDescription]);
}
return persistentCoordinator;
}
I call it in
- (void) initCoreDataProperties
{
NSError *error;
// Creating the Managed Object Model from momd
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:TBCoreDataModelFileName withExtension:#"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// Creating the Encrypted Store Persistent Coordinator
_persistentStoreCoordinator = [EncryptedStore makeStoreWithOptions: [self persistentStoreOptions]
managedObjectModel: self.managedObjectModel
error: &error];
First, do not check the error for an error state. Only check the return of the call to -addPersistentStoreWithType.... The error can be populated even in a non-error condition.
Your code looks fine so I suspect if you turned off the encrypted store and used an Apple provided SQLite store then it would work fine. Which means the issue is with that third party code.
Since the third party code is not providing you with an error or a NSPersistentStore then it is failing poorly and you need to open a bug against the code base so that the author can address it.
Or you can walk through that third part code and see where it is failing and why.

Migrate iCloud local store to iCloud with Magic Record

Can anyone please tell me, how to migrate local store to icloud, when using Magic record. Will magic record has any support for that. Is there any documentation for magic Record.
I am struggling from three days.
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:#"ABC.sqlite"];
NSManagedObjectModel *model = [NSManagedObjectModel MR_defaultManagedObjectModel];
NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSURL *seedStoreURL = [documentsDirectory URLByAppendingPathComponent:#"ABCCloud"];
NSError *seedStoreError;
NSDictionary *seedStoreOptions = #{NSReadOnlyPersistentStoreOption: #YES};
NSPersistentStore *seedStore = [coord addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:seedStoreURL options:seedStoreOptions error:&seedStoreError];
NSDictionary *iCloudOptions =
#{NSPersistentStoreUbiquitousContentNameKey: #"CloudStore"};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSError *error = nil;
NSPersistentStore *iCloudStore = [coord migratePersistentStore:seedStore
toURL:storeURL
options:iCloudOptions
withType:NSSQLiteStoreType
error:&error];
Sorry I removed magical record code now. This is the code i write for migration, but persistent store is nil..
This is the error
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: 'nil is not a valid persistent
store'

Database won't open after changing how Core Data initializes

This is the current initialisation code in my app, based on cs193p by Paul Hegarty:
UIManagedDocument *database = nil;
if (!database) {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"stdDatabase"];
database = [[UIManagedDocument alloc] initWithFileURL:url];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
database.persistentStoreOptions = options;
}
if (![[NSFileManager defaultManager] fileExistsAtPath:[database.fileURL path]]){
[database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
completionBlock(database);
}];
} else if (database.documentState == UIDocumentStateClosed){
[database openWithCompletionHandler:^(BOOL success){
completionBlock(database);
}];
} else if (database.documentState == UIDocumentStateNormal) {
completionBlock(database);
}
This is the new initialisation code I want to use, based on the "Core Data" book by Marcus Zarra:
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"Corrida_de_Leitos" withExtension:#"momd"];
ZAssert(modelURL, #"Failed to find model URL");
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
ZAssert(mom, #"Failed to initialize model");
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
ZAssert(psc, #"Failed to initialize persistent store coordinator");
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[moc setPersistentStoreCoordinator:psc];
[self setManagedObjectContext:moc];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *storeURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
storeURL = [storeURL URLByAppendingPathComponent:#"stdDatabase"];
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setValue:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setValue:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
NSError *error = nil;
NSPersistentStoreCoordinator *coordinator = [moc persistentStoreCoordinator];
NSPersistentStore *store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error];
if (!store) {
NSLog(#"Error adding persistent store to coordinator %#\nUSERINFO:\n%#", [error localizedDescription], [error userInfo]);
}
dispatch_sync(dispatch_get_main_queue(), ^{
[self contextInitialized];
});
});
This is the error I get when adding the store to the coordinator:
'Error adding persistent store to coordinator The operation couldn’t be completed. (Cocoa error 256.)
USERINFO:
{
NSSQLiteErrorDomain = 14;
NSUnderlyingException = "unable to open database file";
}'
How do I fix the new code so it is able to open the old database file?
The UIManagedDocument will have created this sqlite file inside the UIManagedDocument package (a directory) so you need to get the URL to the original sqlite file and use this as the URL in the new version of the app, if necessary use the NSFileManager to move the file to another location, but that's not really necessary. See example directory structure from UIManagedDocument, one for local and one for iCloud synced store. If its not iOS7 then check because the structure/names may be different.
In theory you can get the actual store file name from UIManagedDocument.persistentStoreName and simply append that to UIManagedDocument.fileURL - but in practice this omits the StoreContent subdirectory I think.
Usually UIManagedDocument would have created the file at "stdDatabase/StoreContent/persistentStore". But to be sure run the original version of the app in the simulator and then check exactly what path is used to create the store file.
The actual sqlite file should work fine as long as you open using the same options.
The problem is that UIManagedDocument creates the stack for you and saves your database file with a format not compatible with a SQLite file, if you want to use a SQLite file you first have to migrate the store (file), the NSPersistentStoreCoordinator have a method called migratePersistentStore:toURL:options:withType:error: which can do this migration.

Core Data populated in Simulator but not moving to Device

I've found lots of explanations and help here on stack but so far, no luck.
Pretty much, my myapp.sqlite (that I pre-populated) works fine on the simulator but when I run it on the iPad, it's empty.
So after trying different things, this is the closest I got :
I copy the sqlite db into the Bundle but I move it to the Library folder.
On my AppDelegate I do this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSLog(#"Starting to save the DB to another location");
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
NSString *targetPath = [libraryPath stringByAppendingPathComponent:#"myDB.sqlite"];
if (![[NSFileManager defaultManager] fileExistsAtPath:targetPath]) {
// database doesn't exist in your library path... copy it from the bundle
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:#"myDB" ofType:#"sqlite"];
NSError *error = nil;
if (![[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:targetPath error:&error]) {
NSLog(#"Error: %#", error);
}
}
return YES;
}
then on the PersistentStoreCoordinator, I do this
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
NSString *targetPath = [libraryPath stringByAppendingPathComponent:#"myDB.sqlite"];
// NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"naccApp.sqlite"];
// NSURL *storeURLLocal = [[NSBundle mainBundle] URLForResource:#"myDB" withExtension:#"sqlite"];
NSURL *storeURL = [NSURL URLWithString:targetPath];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
When I try to use the DB, (fetchRequest), I get an error :
CoreData SQL stores only support file URLs (got /var/mobile/Applications/2EB2AADD-DF9D-475F-A05E-BB138502471F/Library/myDB.sqlite).
The message is clear but I've tried almost all the help here and still nothing.
Needless to say, I'm new to Core Data, so please forgive the ignorance.
Oh, I'm using xCode 5.
Thx
NSURL *storeURL = [NSURL URLWithString:targetPath];
Should be:
NSURL *storeURL = [NSURL fileURLWithPath:targetPath];
so that you generate a file URL instead of a web URL.

Error message in Core Data

I've been using Core Data in my app, and suddenly I got en error message as follows:
Attempt to add read-only file at path file://localhost/var/mobile/Applications/xxx-xxx-xxx../.app/MyModel.sqlite read/write. Adding it read-only instead. This will be a hard error in the future; you must specify the NSReadOnlyPersistentStoreOption.
The error occurred in the method below:
-(NSPersistentStoreCoordinator*)persistentStoreCoordinator{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSString* path= [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"MyModel.sqlite"];
NSURL* storeURL = [[NSURL alloc] initFileURLWithPath:path];
NSError* error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
Before today, I have never seen this error message before. Can any one tell me what went wrong? Thank you for your time.
The answer lies in
[[NSBundle mainBundle] resourcePath]
The mainBundle resourcePath is where to load data contained in your application IPA file, it is and will always be readonly, you need to create the sqllite file in the application document folder
you can replace it by
NSURL *storeURL = [[self applicationPrivateDocumentFolderURL] URLByAppendingPathComponent:#"MyModel.sqlite"];
- (NSURL *)applicationPrivateDocumentFolderURL
{
if (!m_privateDocumentFolderURL) {
NSString *applicationPrivateDocumentFolderPath = [[NSFileManager defaultManager] applicationSupportDirectory];
m_privateDocumentFolderURL = [[NSURL alloc] initFileURLWithPath:applicationPrivateDocumentFolderPath];
}
return [[m_privateDocumentFolderURL copy] autorelease];
}
For anyone landing here because they're trying to ship a read-only database with their app, here's the code to create the store
NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption: #(true),
NSInferMappingModelAutomaticallyOption: #(true),
NSReadOnlyPersistentStoreOption: #(true))
// The NSReadOnlyPersistentStoreOption is the important one here
NSPersistentStore * seedStore =[coordinator
addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"Seed"
URL:[NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource:#"Seed" ofType:#".sqlite"]]
options:options
error:&error];
Also, when you create your database, you'll probably have your journal mode set to WAL, which is not compatible with a read-only db. You have 2 choices:
add NSSQLitePragmasOption : #{#"journal_mode" : #"DELETE"}} to your options
or open the seed database with a tool such as Liya and run "PRAGMA wal_checkpoint(RESTART)" followed by "PRAGMA journal_mode = DELETE

Resources