coredata not working on xcode 4.3? - iphone

just wondering if someone else came across this.
I got this piece of code that used to work brilliant in previous xcode versions.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:#"mydb.sqlite"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"mydb" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
[self addSkipBackupAttributeToItemAtURL:storeUrl];
NSError *error;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil 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.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator;
}
I would expect the following from this:
an empty "mydb" to be created from scratch if there is no "mydb.sqlite" in my bundle.
if a "mydb.sqlite" exists in my main bundle then i would expect it to be copied in the specified diretory.
if the "mydb.sqlite" is not compatible with my xcdatamodel the app must crash.
however this works only with already created db's previously.
If for example i try to put a random db named "mydb.sqlite" to my bundle and delete the original one then,
the app doesnt crash!!! a blank db is created and the new db is ignored.
This is completely wrong as it goes against my code.
In addition if I add back the original db nothing happens and the app just creates a blank db.
(and yes I do clean my project, delete the sim app, and even delete the build folder before any change occurs!)
any ideas??

There is a difference between your code and your expectation as you've stated it.
Here is your expectation:
an empty "mydb" to be created from scratch if there is no "mydb.sqlite" in my bundle.
if a "mydb.sqlite" exists in my main bundle then i would expect it to be copied in the specified directory.
if the "mydb.sqlite" is not compatible with my xcdatamodel the app must crash.
Here is what your code does:
Look for mydb.sqlite in the Documents directory
If mydb.sqlite does not exist, copy it from the main bundle (if it exists in the main bundle)
Create a new persistent store at ~/Documents/mydb.sqlite
The main problem I think you're experiencing is because of Step 3. addPersistentStoreWithType:... creates a new store at the URL provided. You instead need to requery the existence of the file (to check that there is now a file existing which may have been copied from the MainBundle) and if it exists, then use [persistentStoreCoordinator persistentStoreForURL:storeURL] instead of creating a new persistent store.
Once you have fixed this, the other problems should be more easily traceable. I suggest adding many more NSLog's in to your code, so you can see whether the code is following the execution path you expect. E.g. log each new object you create so you can easily see if any of them are nil. When you copy your bundle db, add an NSError pointer rather than NULL, and then if the error is non-nil, log it.

the answer to the question/ observation is simple.
downgrade to 4.2.1
I have restored my xcode to 4.2.1 (thank God for Time Machine) and now ALL is as expected.
I will file a bug report to Apple later on today.

Check that your default db is actually being included in the bundle - i.e. it is checked as included in the project, and is in the copy files build phase. I rearranged a project and an SQLite file, whilst bring copied into the project structure, was not included in my project - this would cause the behaviour you are seeing.

Related

sqlite prepare statement error - no such table

I'm having some difficulty with my sqlite prepare statement. I get an error saying my table does not exist, although I've checked in multiple places for it, and it does exist, so I'm confuzzled.
The file is in the correct iPhone Simulator Application folder
The file is added to my project and viewable in the project navigator
It is also in my build phases- Copy Bundle Resources area.
I've cleaned and started running again.
The database exists and running my sql statement gets me just the
results I expected.
- (NSMutableArray *) getMyWorkout{
NSMutableArray *workoutArray = [[NSMutableArray alloc] init];
#try {
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSString *dbPath = [[[NSBundle mainBundle] resourcePath ]stringByAppendingPathComponent:#"IOSDB.sqlite"];
NSLog(#"Db path is %#",dbPath);
BOOL success = [fileMgr fileExistsAtPath:dbPath];
if(!success) {
NSLog(#"Cannot locate database file '%#'.", dbPath);
}
if(!(sqlite3_open([dbPath UTF8String], &db) == SQLITE_OK)){
sqlite3_close(db);
NSLog(#"Failed to open database with message '%s'.", sqlite3_errmsg(db));
}
const char *sql = "SELECT Id, type, difficulty, duration, description FROM workoutTbl";
sqlite3_stmt *sqlStatement;
if(sqlite3_prepare(db, sql, -1, &sqlStatement, NULL) != SQLITE_OK){
NSLog(#"%s Prepare failure '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(db), sqlite3_errcode(db));
} //...
When I run it, I get the file path and the following error
2013-02-01 18:07:08.060 TriShake[9251:c07] -[MyWorkoutList getMyWorkout] Prepare failure 'no such table: workoutTbl' (1)
I've checked out these other questions, but have been unable to find a solution
Sqlite Prepare Failed: no such table<tablename>
Sqlite3 gives "no such table" error on iPhone
I understand sqlite3_open() creates an empty database for you if the database path does not exist, but i know it exists, so frustration ensues. Any help or guidance you could give me would be much appreciated.
In terms of your immediate problem, it's going to be something simple.
You say you've "cleaned and built again", but have you actually removed the old app from your simulator? Either remove the app manually, or, easier, just reset the simulator entirely by choosing "reset content and settings" from the "iOS Simulator" menu. Sometimes Xcode is not good about knowing what files to copy over (particularly in a case like this where your running it on the device may be changing the timestamp of the file in the simulator's bundle!)
Run the app again.
If the app doesn't work as expected, open up the database in the simulator folder from the Mac and check out the database to make sure the table is there and precisely as you expected it to be. So navigate to the app, open the bundle (you may have to choose the "show package contents" option), confirm the existence of the database, but just as importantly, open it up this particular copy of the database in your favorite Mac sqlite3 tool of choice and confirm the existence of the table there.
Let us know what you find. Again, it's got to be something simple such as:
Perhaps the process of rebuilding the app was not reinstalling everything; I've occasionally had problems where Xcode elected to not re-copy something during the install on my simulator;
Perhaps your database in your project was accidentally put in a subdirectory, worse, you might have two copies sitting in different directories;
Perhaps the database in your Xcode project is missing (or has a typo or (esp in the case of the device) has incorrect filename capitalization) in the name of the table or file;
Etc.
For a lot of these errors, you won't notice the problem until you completely reset the simulator itself. There are a million little things it could be, but hopefully completely resetting the simulator and starting over will help you find the issue. It's always something simple when it comes to these sorts of issues.
Some other minor observations:
You probably should not be opening databases from the bundle. Programmatically copy it from the bundle to the Documents folder, and open the database from there. I know it seems unnecessary, but it's important for a myriad of reasons (if db changes during operation of the app, if db accidentally gets created on you, don't let Xcode get confused about things that changed (even if only file timestamps) in the bundle changing behind Xcode's back, etc.)
You should, if you need the database to be there, use sqlite3_open_v2, using either SQLITE_OPEN_READWRITE or SQLITE_OPEN_READONLY for flags (but do not include SQLITE_OPEN_CREATE). It causes headaches to ever give sqlite a chance to create a blank database for you, or otherwise modify it, so never give it an opportunity to so.
I have encounter the same problem as yours. If the IOS can not find the designated database file, defaultly it will create one for you instead of throwing an error. So you must open the database file IOS created for you which is blank so it off course contain the table you expected.
what I deal with it :
1 you have to bundle the resource file named *.sqlite3 into your project
2 Then You have to use [NSBundle mainBundle] pathFordirectory...... function to search your proper database file.
then you can open the database file you expected and can operate it properly
Best regards,
Not enough rep to comment on Jack's post, but that helped me.
In my case, I had mistyped my path for resource extension:
// Wrong
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:#"productList"
ofType:#"sqlite3"];
// Should have been (added db ext)
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:#"productList"
ofType:#"db"];
I would always get past the:
if (sqlite3_open([sqLiteDb UTF8String], &_database) == SQLITE_OK))
because it was automatically creating a db file for me.

Correct way to remove a UIManagedDocument

I have a feature in my app whereby the User can reset everything on the app by click of a button. At this point, instead of trying to delete all the Core Data relations (Cascade Delete) and other complications, I decided to actually remove the entire UIManagedDocument using this piece of code
-(void)cleanUpDocument
{
[[NSFileManager defaultManager] removeItemAtPath:[self.document.fileURL path] error:nil];
}
This should remove the Document I assume? But it sometimes throws an error. And the weird part is that, when I try to re-create the Document the next time, I get an error saying "Can't create File, File already Exists". The code that i use to create the Document is this :-
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.document.fileURL path]]) {
[self.document saveToURL:self.document.fileURL
forSaveOperation:UIDocumentSaveForCreating
completionHandler:nil]
}
My question is this :- what is the best/correct way to remove/delete an entire UIManagedDocument and start fresh on next successful login?
Thanks in advance.
I just had the same issue and tried exactly your approach at first, only to be greeted by similar errors. From what I gather, it's not the best (or at least not necessary) to delete the entire UIManagedDocument, but rather only the underlying persistent store (while keeping this managedObjectContext in sync, of course).
This answer worked for me: https://stackoverflow.com/a/8467628/671915
The problem is that you're removing the file while some objects still hold a reference to it and are keeping it open.
The correct solution is to do this:
[document closeWithCompletionHandler:^(BOOL success){
if([[NSFileManager defaultManager] fileExistsAtPath:[document.fileURL path]]){
[[NSFileManager defaultManager] removeItemAtURL:document.fileURL error:nil];
}

Core Data: Pre-populate SQLite Entity with Image

I am already pre-populating data for my application by first creating the database through core data, then populating that initialized file with SQLite Manager. Is it possible to pre-populate images in a SQLite table for use in core data as well?
My initial thought is to insert the images as a blob through SQLite Manager. Then based on this post, it looks like I would need to set the type to binary and import with UIImage initWithData:.
Is this doable, and if so, is this the appropriate method?
Pre-populating images to a SQLite database for use with Core Data turns out to be fairly trivial.
First configure your Core Data application, implementing your attribute to contain images as a "Binary" type. Build the application and navigate to your view utilizing Core Data within the application simulator. This will initialize the SQLite database as required for use with Core Data (assuming you've implemeted the persistentStoreCoordinator as follows).
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"YourDBName.sqlite"];
// Set up the store.
// For the sake of illustration, provide a pre-populated default store.
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn’t exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"YourDBName" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error;
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
return persistentStoreCoordinator;
}
Navigate to the application data at "Users//Library/Application Support/iPhone Simulator/User/Applications/". If you sort the folder by "Date Modified," your application will have the latest date (assuming you haven't built any other applications in the mean time). Enter the application folder and the initialized < YourDBName.sqlite > will reside in the Documents folder. Copy the SQLite database to another location (like your desktop) and delete the original file (this is necessary to allow Core Data to reload the pre-populated SQLite database you are about to create).
Open < YourDBName.sqlite > with your favorite SQLite editor (the SQLite Manager plugin for Firefox is an adequate, and free, option). Add entries to your table, inserting any images as a "BLOB."
Within XCode add < YourDBName.sqlite > as an existing file. Core Data will copy this file to the application data folder the next time you launch your application if it does not already exist there (you deleted the original right?).
Access your pre-populated images within your code with [UIImage imageWithData:< DataObject >.< ImageAttributeName >
How big are your images going to be? If they are fairly large you may be better served by storing the image in the file system and keeping a reference to its location in core data.
If the images will always exist in your app then you can package them with your bundle. If not (e.g. a user can remove unwanted images) you may have to rely on pulling the images in on first use.

replaceItemAtURL fails without error on iOS but works fine on OSX

I'm implementing a manually-triggered migration process for a CoreData-based app, and after the migration completes successfully, I'm trying to move the migrated DB back over the top of the original one using replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error:.
The problem is that on iOS, nothing I do will make this method return YES, however it also never puts anything into the error pointer to allow you to see what's going wrong.
I'd read things elsewhere (e.g. http://www.cocoabuilder.com/archive/cocoa/287790-nsdoc-magic-file-watcher-ruins-core-data-migration.html) indicating that not shutting down all the CoreData objects (e.g. NSMigrationManager, NSManagedObjectModel etc) before attempting the replace might be the cause, but that wasn't it. I even implemented a little two file create-and-swap thing that didn't involve CoreData DBs at all to verify that the CoreData stuff didn't have anything to do with it.
I then noticed in the official documentation that the newitemURL is supposed to be in a directory deemed appropriate for temporary files. I assumed that that meant a directory returned by URLForDirectory:inDomain:appropriateForURL:create:error: using NSItemReplacementDirectory as the search path.
That didn't work either! I ended up falling back to implementing the replacement logic using separate operations, but this is non-atomic and unsafe and all that bad stuff.
Does anyone have a working snippet of code that runs on iOS that either return YES from a call to replaceItemAtURL or actually puts error information into the error pointer?
Any help much appreciated.
EDIT - Test code included below. This runs in application:didFinishLaunchingWithOptions: on the main thread.
NSFileManager *fm = [[NSFileManager alloc] init];
NSError *err = nil;
NSURL *docDir = [NSURL fileURLWithPath:[self applicationDocumentsDirectory]];
NSURL *tmpDir = [fm URLForDirectory:NSItemReplacementDirectory
inDomain:NSUserDomainMask
appropriateForURL:docDir
create:NO
error:&err];
NSURL *u1 = [docDir URLByAppendingPathComponent:#"f1"];
NSURL *u2 = [tmpDir URLByAppendingPathComponent:#"f2"];
NSURL *repl = nil;
[fm createFileAtPath:[u1 path]
contents:[[NSString stringWithString:#"Hello"]
dataUsingEncoding:NSUTF8StringEncoding]
attributes:nil];
[fm createFileAtPath:[u2 path]
contents:[[NSString stringWithString:#"World"]
dataUsingEncoding:NSUTF8StringEncoding]
attributes:nil];
BOOL test = [fm replaceItemAtURL:u1 withItemAtURL:u2 backupItemName:#"f1backup"
options:0 resultingItemURL:&repl error:&err];
// At this point GDB shows test to be NO but error is still nil
I have experienced issues with all the NSFileManager methods using an URL on iOS. However, all the methods using Path work. So I think you should use removeItemAtPath:error:and copyItemAtPath:toURL:error: for that purpose.
Hope it helps
In mac file system is not case sensitive, but in IOS it. Even though you cant have two files with same name but with different case at one location, the path is case sensitive. So if file is has .JPEG and in your code you are passing link with .jpeg it will fail.
It may not be the case with you but just what to share
Although strangely it should give you error.

NSFileManager creating folder (Cocoa error 513.)

I'm trying to create a folder inside the /sounds folder of my app.
-(void)productPurchased:(UAProduct*) product {
NSLog(#"[StoreFrontDelegate] Purchased: %# -- %#", product.productIdentifier, product.title);
NSFileManager *manager = [NSFileManager defaultManager];
NSString *bundleRoot = [[NSBundle mainBundle] bundlePath];
NSError *error;
NSString *dataPath = [NSString stringWithFormat:#"%#/sounds/%#", bundleRoot, product.title];
if (![manager fileExistsAtPath:dataPath isDirectory:YES]) {
[manager createDirectoryAtPath:dataPath withIntermediateDirectories:YES attributes:nil error:&error];
NSLog(#"Creating folder");
}
NSLog(#"%#", error);
}
But I get this error:
Error Domain=NSCocoaErrorDomain Code=513 "The operation couldn’t be completed. (Cocoa error 513.)" UserInfo=0x175120 {NSFilePath=/var/mobile/Applications/D83FDFF9-2600-4056-9047-05F82633A2E4/App.app/sounds/Test Tones, NSUnderlyingError=0x117520 "The operation couldn’t be completed. Operation not permitted"}
What am I doing wrong?
Thanks.
If you search Google on the error domain NSCocoaErrorDomain you find that the code 513 translates to the error NSFileWriteNoPermissionError.
This provides you with the critical clue for solving this problem:
This is the bundle directory containing the application itself. Because an application must be signed, you must not make changes to the contents of this directory at runtime. Doing so may prevent your application from launching later.
Specifically, you cannot modify the contents of a compiled app's bundle folder. This is because the bundle is the compiled application.
When you eventually distribute the app through the iTunes App Store, the application has a digital signature that validates the contents of the app. This signature is generated at compile time.
If you try to change the bundle after compilation, the app changes and the digital signature is no longer valid. This invalidates the application — who knows what code is in there, right? — and so Apple has set up iOS to throw an error if you try to modify the application.
Instead of writing to the bundle, your app can write to one of three accepted app-specific folders: <Application_Home>/Documents, <Application_Home>/tmp and <Application_Home>/Library/Caches.
Most likely, you will want to write to the <Application_Home>/Documents folder.
These folders are only accessible to your app. No other app can access the contents of these folders. (Likewise, your app cannot access another app's folders.)
You can set up your app to allow the end user to manage access to file data through iTunes, via desktop file sharing support.
This is because you should never modify the bundle of your application at runtime. Instead, you should have a folder elsewhere where you can add resources.
EDIT:
The error you are seeing is most likely because you cannot write to the bundle.
I encounter the same problem, when using a Log library. Finally, it's path format problem. Check the dataPath format. If it is Case 1, it is valid. In my case, it's Case 2, so I failed to create directory.
// Case 1
/var/mobile/Containers/Data/Application/5FB2CD2D-91DC-4FB2-8D6F-06369C70BB4A/Library/Caches/AppLogs
// Case 2, invalid format
file://var/mobile/Containers/Data/Application/5FB2CD2D-91DC-4FB2-8D6F-06369C70BB4A/Library/Caches/AppLogs
If the dataPath has a prefix, ex: file://, it is invalid.
As for an instance of NSURL, path will return the string like case 1, and absolutePath will return the string like case 2.
I'm still not totally clear on the meaning of the 513 error in my case, but I was getting it when just trying to read an opened file URL using [NSFileHandle fileHandleForReadingFromURL:theUrl error:&err ].
I realized from this answer that with iOS 13, I now need to use startAccessingSecurityScopedResource to access external files that are opened in the app. When I wrapped my file calls as follows, then the error 513 stopped occurring:
if( [myURL startAccessingSecurityScopedResource] )
{
NSFileHandle* myFile = [NSFileHandle fileHandleForReadingFromURL:myURL error:&err ];
// ...Do file reads here...
[theUrl stopAccessingSecurityScopedResource];
}