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
Related
I have a problem on iOS 8.1 with CoreData. On my device I always get this error while the code runs fine on every simulator:
Error Domain=NSCocoaErrorDomain Code=512 "The operation couldn’t be completed. (Cocoa error 512.)" UserInfo=0x15e62bd0 {reason=Failed to create file; code = 1} with userInfo dictionary {
reason = "Failed to create file; code = 1";
}
I already tried to delete the app from my device. I successfully tried to manually create a text file. Here is the code snippet which causes the error:
- (NSPersistentStoreCoordinator*) persistentStoreCoordinator {
NSError* error = nil;
NSURL* storeURL = [[[NSBundle mainBundle] resourceURL] URLByAppendingPathComponent: #"users.sqlite"];
if (_persistentStoreCoordinator != nil)
return _persistentStoreCoordinator;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
error = nil;
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
[_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];
}
return _persistentStoreCoordinator;
}
EDIT:
Thanks for your answer jrturton.
The solution is
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSURL* storeURL = [NSURL fileURLWithPath:[documentsDirectory stringByAppendingString:#"/users.sqlite"] isDirectory:NO];
Your app's bundle is read-only on the device, but not on the simulator.
When you're creating storeURL you are creating a URL that points to the app's bundle. You should be using the documents directory instead.
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.
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.
I used the Master-Detail-Application example at Xcode 4.3.3 to develope a data viewer. The app is downloading several files and append each file to a cell at the tableview. everything works fine. but if the user starts the download again, I need to remove all the previous data from the database and the tableview.
I ended up with the following steps:
remove the old store from coordinator
remove the old sqlite database file
copy the default ( empty ) sqlite db file from the bundle dir into the doc dir
update store coordinator
update tableview.
but now the problem is, that if I do so, the new data is not written into sqlite file.
Please, can someone check my code. The following code is located at the MasterViewController.m ! I working on this since several days and I am totally stuck !!!
Thank you!
NSLog(#"Delete existing database file and copy default from bundle dir!");
NSFileManager* fileManager = [[NSFileManager alloc] init];
fileManager = [[NSFileManager alloc] init];
NSError *error = nil;
//get the documents directory:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
// create database path documents dir
NSString *databasepath = [documentsDirectory stringByAppendingPathComponent:#"MyDatabase.sqlite"];
NSLog(#"Destination Database Path = %#", databasepath);
// create database path bundle dir
NSString *resourceDBFolderPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"MyDatabase.sqlite"];
NSLog(#"Recource Database Path = %#", resourceDBFolderPath);
// remove old store from coordinator
error = nil;
NSManagedObjectModel *managedObjectModel;
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"MyDatabase" withExtension:#"momd"];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// 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 = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]];
NSURL *applicationDocumentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [applicationDocumentsDirectory URLByAppendingPathComponent:#"MyDatabase.sqlite"];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
if (error != nil){
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
NSPersistentStore *persistentStore = [[persistentStoreCoordinator persistentStores] objectAtIndex:0];
[persistentStoreCoordinator removePersistentStore:persistentStore error:&error];
if (error)
if (error != nil){
NSLog(#"error removing persistent store %# %#", error, [error userInfo]);
}
// Remove old database file
[[NSFileManager defaultManager] removeItemAtPath: [documentsDirectory stringByAppendingPathComponent: #"MyDatabase.sqlite"] error: &error];
if (error != nil){
NSLog(#"delete Database status: %#", [error localizedDescription]);
}
// copy default empty database file into documents dir
[fileManager copyItemAtPath: resourceDBFolderPath toPath: databasepath
error: &error];
if (error != nil){
NSLog(#"copy default Database status: %#", [error localizedDescription]);
}
// then update storecoordinator
// add clean store file back by adding a new persistent store with the same store URL
if (![persistentStoreCoordinator addPersistentStoreWithType: NSSQLiteStoreType
configuration: nil
URL: storeURL
options: nil
error: &error]) {
NSLog(#"failed to add db file, error %#, %#", error, [error userInfo]);
}
NSLog(#"Database replacement end!");
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];