Core Data deleting problem when closing app completely - iphone

Hi I do have a problem with my Core Data storage!
I delete it the following way like I found it here an stack overflow:
NSFetchRequest * allFriends = [[NSFetchRequest alloc] init];
[allFriends setEntity:[NSEntityDescription entityForName:#"Friend" inManagedObjectContext:self.managedObjectContext]];
[allFriends setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * error = nil;
NSArray * friends = [self.managedObjectContext executeFetchRequest:allFriends error:&error];
[allFriends release];
//error handling goes here
for (NSManagedObject * Friend in friends) {
[self.managedObjectContext deleteObject:Friend];
}
this seams to work perfect at runtime!
my tableview (which I manage with NSFetchedResultsController) clears and all is fine it looks!
Also when I hit the home button and start it back up it works.
BUT if I even close it from the multitasking list (so completely close it) and start it back up all is back in the tableView again!
could anybody help me out with this?

Your code is fine but you have forgotten to commit all changes (objects were removed) made to database. So, you should add following lines to your code and after reopening the app your db will not contain that objects:
NSError *error;
if (![self.managedObjectContext save:&error])
{
// Update to handle the
NSLog(#"Unresolved error %#", error);
exit(-1); // Fail
}
Because all changes are stored in memory, don't forget to save the managed object context after some important or critical changes were made. Before you commit your changes, the database/presistent-store file will be in the previously saved state.

Are you saving the managedObjectContext at any point before quitting? Normally you would save the context when the application enters the background or terminates.

Related

Lost data with multiple NSManagedObjectContexts on iOS7

I'm in the progress of updating an existing app for iOS 7 and I've been having some issues with Core Data saving objects. It's a fairly straightforward master-detail style data entry app that uses Core Data for the storage.
When adding a new record I use a second (temporary) managed object context to prevent the record appearing in the list before the record is saved. When a record is added and saved it is visible in the list as expected. However if I exit the app (it doesn't run in the background) and then restart it the record is no longer present. The record is present in the database (visible using the SQLite Manager Firefox plugin anyway), but it just doesn't show in the app.
I've managed to reproduce this using the code that Xcode produces when creating a new project. I've created a new master-detail application and ticked the Use Core Data box to get the example code, then made the following changes:
Add the following to MasterViewController.m
-(void)save:(NSManagedObjectContext*)context
{
if (context != [self.fetchedResultsController managedObjectContext])
{
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserver:self selector:#selector(addControllerContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:context];
}
NSError *error;
if (![context save:&error])
{
abort();
}
if (context != [self.fetchedResultsController managedObjectContext])
{
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:context];
}
}
- (void)addControllerContextDidSave:(NSNotification*)saveNotification
{
[[self.fetchedResultsController managedObjectContext] mergeChangesFromContextDidSaveNotification:saveNotification];
}
Replace the supplied insertNewObject in insertNewObject with the following to create a new temporary context for adding
- (void)insertNewObject:(id)sender
{
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
context.persistentStoreCoordinator = [[self.fetchedResultsController managedObjectContext] persistentStoreCoordinator];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
// If appropriate, configure the new managed object.
// Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
[newManagedObject setValue:[NSDate date] forKey:#"timeStamp"];
// Save the context.
[self save:context];
}
I also set the app to not run in the background.
If I run this against iOS 6 it behaves as expected i.e. I tap Add and a new record appears, then exit and restart the app and the record is still present.
However if I run the same code against iOS 7 it doesn't work correctly. Tapping Add causes the new record to appear, but if I exit and them restart the app the record is not shown. As mentioned above it is present in the database however.
Interestingly, I've discovered that it might be in some way related to the change in the journaling mode of the SQLite database. If I add the following options in the call to addPersistentStoreWithType I get the expected behaviour running on iOS 7
NSDictionary *options = #{ NSSQLitePragmasOption : #{#"journal_mode" : #"DELETE"} };
So, to the questions (and thanks for reading this far!)
Has anyone else seen this behaviour (or is anyone able to reproduce it based on the description above)?
Is there something wrong with the way I am using a temporary context that I was just lucky with prior to iOS 7, or does this look like an issue with the Core Data framework on iOS 7?
Cheers
Neil
Edit 1:
In answer to Wain's question about saving the main MOC, I was under the impression that this isn't actually necessary because the data is already saved, the merge just updates the already saved changes from the temporary context in to the main context. That said the test code does contain the following methods and saveContext is called on shutdown, however [managedObjectContext hasChanges] returns false so nothing actually gets done at this point
-(void)applicationWillTerminate:(UIApplication *)application
{
// Saves changes in the application's managed object context before the application terminates.
[self saveContext];
}
-(void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges])
{
if (![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();
}
}
}
}
It seems to be fixed when you save your main context after merging changes:
- (void)addControllerContextDidSave:(NSNotification*)saveNotification
{
[[self.fetchedResultsController managedObjectContext] mergeChangesFromContextDidSaveNotification:saveNotification];
[self save:[self.fetchedResultsController managedObjectContext]];
}
UPDATE: this error was caused by using cache in NSFetchedResultsController. So, the data isn't lost, it's just not displayed by your NSFetchedResultsController. Further investigation is needed to find out why cache isn't updated when its MOC merges changes, but isn't saved.

iPhone - App crashes when trying to save new objects after deleting objects in CoreData

Okay, I'm having a problem saving after I've deleted all the objects I have stored in CoreData. I have no problem with saving if I don't delete anything, but as soon as I ask it to delete all the objects (everything deletes with no errors or problems), and then try saving again, it crashes and just gives me a program received signal: SIGABRT. Here's my code.
- (void)deleteStoredData
{
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:MOVIE_LIST inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Fetch the records and handle an error
NSError *error;
NSUInteger count = [managedObjectContext countForFetchRequest:request error:&error];
if (count) {
for (int i = 0; i < count - 1; i++) {
NSManagedObject *eventToDelete = [self.listOfMovies objectAtIndex:i];
[managedObjectContext deleteObject:eventToDelete];
}
}
[request release];
}
I thought it might be me comparing self.listOfMovies to the objects stored, so I did a fresh fetch, copied it to a temp NSMutableArray, then replaced self.listOfMovies with temp. But no changes, still crashes. Did I some how delete the entire record and it no longer exists?
What I want to do is load everything onto the app, then delete all the objects in CoreData, so that when the app closes (or terminates) it saves all the new data in the records. Am I doing this correctly, or is there a much easier way to do this? Oh yea, and I only have one entity that holds 5 NSStrings, so nothing to complicated.
Thanks in advance everyone.
Possibly, when you call the save method, there might be some mixup with some variable such as the managedObjectContext.
Did you try saving right after the deletion (i.e. in your deleteStoredData method above)?
BTW, I would also go with Christopher's code;-).
Deleting managed objects with a for loop like that is error prone and probably corrupting your managedObjectContext. Try the following:
NSFetchRequest * fetch = [[[NSFetchRequest alloc] init] autorelease];
[fetch setEntity:[NSEntityDescription entityForName:MOVIE_LIST inManagedObjectContext:context]];
NSArray * result = [context executeFetchRequest:fetch error:nil];
for (NSManagedObject * event in result) {
[context deleteObject:event];
}
Since you are already have all of managedObject in your array, you do NOT need to do another fetch, just delete them with the code below should be OK.
for (NSUInteger i = 0; i < [self.listOfMovies count] - 1; i++) {
NSManagedObject *eventToDelete = [self.listOfMovies objectAtIndex:i];
[managedObjectContext deleteObject:eventToDelete];
}
If it still have problem, would you please tell me how many managedObjectContext do you have in your App? Are you deleting or saving it in a background thread?
In addition, would you please also post the crash log and the information by type "bt -> enter key" in console after your App crashed?

Coredata - "NSObjectInaccessibleException - CoreData could not fulfill a fault"

I am new to Core data and still figuring out the nuts and bolts and this error has been bugging me for hours and I cant seem to find a solution. Any help is greatly appreciated.
The problem is like this
I have two views which fetch data from the server and Update the UI. I have set up the flow this way
view1 -> Send HTTP Req from Server - Receive Callback -> Save Data To Coredata -> Read From Core Data and display on the UI (callback and saving/reading Coredata happen in ViewController)
view2 -> Send HTTP Req from Server - Receive Callback -> Save Data To Coredata -> Read From Core Data and display on the UI (callback and saving/reading Coredata happen in ViewController)
View 2 repeats this process every 3 seconds as this is a auto refresh screen.
The problem is whenever I try to switch between views 1 and 2 real fast, it crashes the app with above error. If I wait for a few seconds on each view (wait for data to be fetched from the server), it works fine. Am I doing something wrong, what do I need to modify?
- (void) refreshData {
[super refreshData];
[[UserDataFactory sharedSingleton] refreshLoggedInUserDataAndRespondTo:self user:self.user];
}
- (BOOL) refreshDataCallback:(QExtendedHTTPOperation*)responseOperation {
[self saveToCoreData: responseOperation.responseArray];
NSMutableArray *tmp = [[NSMutableArray alloc] initWithArray:[self readFromCoreData]];
[self setData: tmp];
[tmp release];
[self.tableView reloadData];
return YES;
}
- (void) saveToCoreData:(NSArray *) responseArray{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CoreView1" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSError *error;
NSArray *items = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
for (NSManagedObject *managedObject in items) {
[self.managedObjectContext deleteObject:managedObject];
}
for (int i=0; i<[responseArray count]; i++) {
CoreView1 *coreView1_ = [NSEntityDescription insertNewObjectForEntityForName:#"CoreView1" inManagedObjectContext:self.managedObjectContext];
coreView_.id = [[responseArray objectAtIndex:i] id];
[self.managedObjectContext insertObject:coreView1_];
}
[self saveContext:self.managedObjectContext];
}
- (NSArray *) readFromCoreData{
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CoreView1" inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSError *error;
NSMutableArray *fetchedObjects = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
[fetchRequest release];
return [fetchedObjects autorelease];
}
This is the sample code I'm using, even View2 has the same callbacks and follows the same flow.
Edit 1
Forgot to mention this earlier, I always get the error in saveToCoreData method. Also, one more thing to note is that If I remove the code to delete objects it all works fine (I need to remove all the existing data from the table before I save the updated data). Not sure whats happening though.
The reason removing your delete code makes it work is because you are removing the data in the persistent store without updating the other view that still has managed object instances tied to that data still in memory. Remember, while Core Data deals with objects, each object has to have a row in the database behind it. When you delete that row, Core Data gets angry.
So, to fix this problem, and still delete your data, you should have your views listen for NSManagedObjectContextWillSaveNotification and/or NSManagedObjectContextDidSaveNotification notifications and update your views with the most up to date versions of data in your store. It is at this point you should throw away any Core Data objects your views are holding onto, and reload them from the store.
I just resolved this error in my code. Seems my cache was corrupted in some way. I used Christopher Pickslay & Keil Gillard's suggestion of deleting or renaming my cache, and voila, crash resolved.
NSFetchedResultsController index beyond bounds
For info, yesterday, I had the same error. I checked in a live version of the app, and it was still there. yikes.
I ran through all the permutations of adding info to the core data stack until the crash occurred.
I then looked at the sqlite file from the app in the simulator library using SqliteManager. I found a data error in one table. This had occurred as there is a starter db used the first time the app was run. The error was in the starter db.
Ran the app again, testing each field in each table. Found several more occurrences. I remember reading this question, and thought maybe someone else could benefit from my mistake.
Your over releasing fetchedObjects in your readFromCoreData method. executeFetchRequest will return to you autoreleased objects anyway. When the current run loop finished executing (when you're jumping from views) then it attempt to release the objects twice (assuming you haven't used your own autorelease pools).
I'd change
return [fetchedObjects autorelease];
to
return fetchedObjects;

Core Data error: _Unwind_Resume called from function _PFFaultHandlerLookupRow in image CoreData

I'm getting this weird error from Core Date and I cant understand why.
The code below is executed when I delete a row of a UITableView.
I pass a string and an object to the method below and it fetches the article in a database table that has that string and has a foreign key to that object. Then I delete that object and reload the table.
- (void)deleteFavorite:(NSString *)link inFolder:(Favorites *)f {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *favsDecriptor = [NSEntityDescription entityForName:#"Favorites" inManagedObjectContext:context];
[request setEntity:favsDecriptor];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(belongsTo == %#) AND (link = %#)", f, link];
[request setPredicate:predicate];
NSError *error = nil;
NSMutableArray *fav = [[NSMutableArray alloc] init];
fav = [[context executeFetchRequest:request error:&error] retain];
if (![context save:&error]) {
NSLog(#"Cannot fetch the story from the fetch request.");
}
NSLog([[fav objectAtIndex:0] title]);
error = nil;
[context deleteObject:[fav objectAtIndex:0]];
if (![context save:&error]) {
NSLog(#"Can't delete the fav! %#", error);
}
}
The app instantly crashes and I get this message in the console.
But when I launch the app afterwards, the row has been deleted.
Detected an attempt to call a symbol in system libraries that is not present on the iPhone:
_Unwind_Resume called from function _PFFaultHandlerLookupRow in image CoreData.
Please help!
Thanks in advance to everyone!
This is probably related to a bug within Core Data itself. I had the same error come up (I asked about it here in SO) and my only fix was to change the keywords in the predicate that still allowed the same results. It took some experimenting to find the right combination. Not ideal, but that's the best answer I can offer based on my experience.
Is it possible that you are holding a reference to the delete object or that the deleted object is an observer and is getting a callback after its been deleted? I had something similar to this recently, though slightly different error message. In my case, I also crashed upon deletion (under some conditions) but when I relaunched the object-to-be-deleted had, in fact, been deleted.
If you haven't already done so, under the Run menu select Stop on Objective-C Exceptions. This helped me track down the root cause of my crash. In my case it was KVO observer getting callback of change of value of a property of deleted NSManagedObject.

Save object in CoreData

I am using CoreData with iPhone SDK. I am making a notes app. I have a table with note objects displayed from my model. When a button is pressed I want to save the text in the textview to the object being edited. How do I do this? I've been trying several things but none seem to work.
Thanks
EDIT:
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValue:detailViewController.textView.text forKey:#"noteText"];
NSError *error;
if (![context 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. 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.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
The above code saves it correctly but it saves it as a new object. I want it to be saved as the one I have selected in my tableView.
You should check out the Core Data Programming Guide. It's hard to know exactly what you want from the question, but the basic idea is:
-(IBAction)saveNote { //hooked up in Interface Builder (or programmatically)
self.currentNote.text = self.textField.text; //assuming currentNote is an NSManagedObject subclass with a property called text, and textField is the UITextField
}
//later, at a convenient time such as application quit
NSError *error = nil;
[self.managedObjectContext save:&error]; //saves the context to disk
EDIT: If you want to edit a preexisting object, you should get the object from the fetched results controller, e.g. NSManagedObject *currentObject = [fetchedResultsController objectAtIndexPath:[self.tableView indexPathForSelectedRow]], then edit that object. I'd also recommend using a custom subclass of NSManagedObject with property declarations, rather than using setValue:forKey, since it's more flexible.