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.
Related
I'm trying to add a sqlite persistent store in Library/Application-Support folder as shown below:
- (NSManagedObjectModel *)managedObjectModel
{
if (!_managedObjectModel)
{
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"delete" withExtension:#"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
}
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (!_persistentStoreCoordinator)
{
#synchronized(self)
{
if (!_persistentStoreCoordinator)
{
NSError *error = nil;
NSURL *storeURL = [[[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject] #"delete.sqlite"];
NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
NSPersistentStore * persistentStore = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];
_persistentStoreCoordinator = persistentStoreCoordinator;
}
}
}
return _persistentStoreCoordinator;
}
This fails with the following error:
CoreData: error: -addPersistentStoreWithType:SQLite configuration:(null)
URL:file:///Users/harshithg/Library/Developer/CoreSimulator/Devices/D9A32558-558A-4E0E-915D-FEF25C772669/data/Containers/Data/Application/9517BBBB-E383-467E-B0E2-8960B734B239/Library/Application%20Support/delete.sqlite
options:(null) ...
returned error NSCocoaErrorDomain(512) with userInfo dictionary { reason = "Failed to create file; code = 2";
I'm on iOS 13.3 and Xcode 11.3. Does anyone know what's happening here?
The Application Support folder does not exist unless you explicitly create it. None of your code creates it. There's an NSFileManager method that will give you the NSApplicationSupportDirectory URL and create it (if it doesn't already exist) at the same time.
https://developer.apple.com/documentation/foundation/nsfilemanager/1407693-urlfordirectory?language=objc
On my first app I used within my persistant store coordinater a NSXMLStoreType.
[storeCooordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:storeURL options:options error:nil];
Now, I like to change to a NSSQLiteStoreType:
[storeCooordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:nil];
The app crashes, if I simply change the store type. So what I have to do? May I have to do once:
check if the old store exists and
if yes convert it to sqlite and
delete afterwards the old xml store?
I have no idea how to convert it to sqlite. The models are the same.
EDIT & ANSWER
I use this solution to migrate once the database (thanks to Volker)
//-> applicationFilesDirectory is the url to the documents directory
NSURL* oldURL = [applicationFilesDirectory URLByAppendingPathComponent:#"DBName1.xml"];
NSURL* newURL = [applicationFilesDirectory URLByAppendingPathComponent:#"DBName2.sqlite"];
NSError *error = nil;
NSFileManager * fileManager = [NSFileManager defaultManager];
//-> if file exists
if ([fileManager fileExistsAtPath:[oldURL path]]) {
NSLog(#"File is here");
NSManagedObjectModel* managedModel = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator* tempCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedModel];
id xmlStore = [tempCoordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:oldURL options:options error:nil];
[tempCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:newURL options:options error:nil];
if ( ![tempCoordinator migratePersistentStore:xmlStore toURL:newURL options:options withType:NSSQLiteStoreType error:&error] ) {
//-> delete the old file from directory
[fileManager removeItemAtURL:oldURL error:NULL];
}
}
You can use migratePersistentStore:toURL:options:withType:error: as described in Apples Core Data documentation.
If this should happen automatically, you will need to add the migration at startup.
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'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
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];