Error after deleting NSManagedObject "The operation couldn’t be completed" - iphone

so I'm having a Core Data & iCloud combined Database that is working fine for me.
Some users though had a certain crash after trying to delete NSManagedObject's that are respresented in an UITableView. In the debug-logs that I attach to the crashreports there is the following error-log before the crash:
2013-05-08 22:38:51.851 MyApp[11819:907] Unresolved error Error Domain=NSCocoaErrorDomain Code=134030 "The operation couldn’t be completed. (Cocoa error 134030.)", {
}
Unfortunately the info about the error: {} is empty and I never had the error myself, so I don't know how to recreate it. It did occur at least 21 times for customers though.
(Whats even worse is, once they reopen the App, some NSManagedObject is in an invalid state and my tableview has problems loading it. But that just at the side)
The actual error is "SIGABRT" in " tableView:commitEditingStyle:forRowAtIndexPath:", here is the respective code:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
ICLog(#"Deleting the row %i,%i from the data source", indexPath.row, indexPath.section);
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
//Save the context
NSError *error;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
}
PS: From the Logs its also clear that this is all run from the main thread.
Anyone here who had this problem too?
UPDATE:
Luckily I had the same error in another App I'm working on just now, in a device I have access too (so far I had never saw this happening on any of my devices), so now I can investigate it while it happens. This is what I found out:
a) it still occurs after switching off icloud in the settings
b) the error occurs everytime I try to delete or insert any NSManagedObject, even if I do it in my Appdelegate right after starting, without ever accessing the data somewhere else
So it seems like something completely stops my databse from being updated. I can catch the above the exception, then the App at least isn't crashing. But of course no changes to the database are saved.

Errors on [context save:] often occur when you try to save an invalid state of your datamodel.
Since you have deleted an object before, i would suspect that this object was in a relation with some other object that still is present, but now has it's relation in an invalid state (nil for a relation that is non-optional)
Checking your delete rules could help, maybe some relation to the object you delete needs to be optional or you have to propagate the deletion correctly.

I will be making some assumptions here.
What I can see is that you delete the CoreData object using the object in fetchedResultsController but you never unset the object from whatever you use as your ViewController iVar fetch storage in numberOfRowsInSection and re-organise your table index after deletion.
Let say your original [indexPath.row count] is 7 then you go on and delete indexPath.row = 5 from your tableView. Then you go on and try to delete another object at indexPath.row = 6 that item exists on your tableView index because you havent re-organised the indexs, but it wont exist in CoreData.... given that now the max count is 6 that the index value is now 5 of what originally was 6.... This could be what your users are experiencing...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [yourFetchedDataVariable count];
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
ICLog(#"Deleting the row %i,%i from the data source", indexPath.row, indexPath.section);
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
//Save the context
NSError *error;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
// the possible solution
[yourFetchedDataVariable removeObjectAtIndex:indexPath.row];
// also now you want to re-order your array index...
[yourTable reloadData]; // and re order your table indexses aswell
}
}

The optional relationship are you sure it is optional? and optional in what sense to Xcode? I ahd this issue once and it was due to a "optional" relationship.

Related

New objects not saving properly when added to tableView

I'm having some issues getting Core Data to save new rows that I add when using a UITextField. Here is my method for inserting objects into my table view. What should happen is when I click the add button, a textfield should be added, and then go right into edit mode. Then when the user clicks done on the keyboard the textfield should end editing and then the textfield should save the entry into core data.
Edit: removed call to textFieldDidEndEditing in the insertNewObject:(id)sender method. It was crashing the app
- (void)insertNewObject:(id)sender {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
TehdaItem *item = [NSEntityDescription insertNewObjectForEntityForName:#"TehdaItem" 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.
// Putting the cell in edit mode
TehdaTableViewCell *editcell;
for (TehdaTableViewCell *cell in [self.tableView visibleCells]) {
if (cell.itemLabel.text == item.itemTitle) {
editcell = cell;
break;
}
}
[editcell.itemLabel becomeFirstResponder];
// The cell needs to call the method textfield did end editing so that it can save the new object into the store
// Save the context.
NSError *error = nil;
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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
Here is my textFieldDidEndEditing method:
- (void)textFieldDidEndEditing:(UITextField *)textField {
TehdaTableViewCell *cell = (TehdaTableViewCell *) textField.superview.superview;
TehdaItem *item = [self.fetchedResultsController objectAtIndexPath:[self.tableView indexPathForCell:cell]];
//TehdaTableViewCell *cell;
item.itemTitle = cell.itemLabel.text;
}
Not really sure where to go from here. Any help would be appreciated.
Thanks.
You can use
NSError *error;
[item.managedObjectContext save:&error];
if (error) {
// Triage the problem and respond appropriately
}
in the - (void)textFieldDidEndEditing:(UITextField *)textField method. But if I were you I'd do some validation before you save the object.

how to unregister an object in a managedObjectcontext?

Is there any method to do this job? I could not find in the document.
To delete an object from database use the following method:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
[managedObjectContext deleteObject:[userResults objectAtIndex:indexPath.row]];
[userResults removeObjectAtIndex:indexPath.row];
// Save the context.
NSError *error = nil;
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. 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();
}
[self.tableView reloadData];
}
Where userResults is the NSMutableArray type, which you use to fetch data and store temporary values
If you mean deleting you can use [context deleteObject:object] where context is your NSManagedContext and object is your NSManagedObject derived object.

Core Data deleting problem when closing app completely

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.

Core Data unresolved error on save

I'm getting an error when saving my core data context, here is the log:
Unresolved error Error Domain=NSCocoaErrorDomain Code=134030 "The operation couldn’t be completed. (Cocoa error 134030.)" UserInfo=0x1937a0 {NSAffectedStoresErrorKey=(
"<NSSQLCore: 0x156410>"
), NSUnderlyingError=0x181a60 "The operation couldn’t be completed. (Cocoa error 4.)", NSFilePath=/var/mobile/Applications/appid/Documents/appname.sqlite}, {
NSAffectedStoresErrorKey = (
"<NSSQLCore: 0x156410>"
);
NSFilePath = "/var/mobile/Applications/appid/Documents/appname.sqlite";
NSUnderlyingError = "Error Domain=NSCocoaErrorDomain Code=4 \"The operation couldn\U2019t be completed. (Cocoa error 4.)\" UserInfo=0x181a30 {NSUnderlyingError=0x108ab0 \"The operation couldn\U2019t be completed. No such file or directory\"}";
}
I receive this error after removing and deleting an object:
[productList removeMProductObject:product];
[[delegate managedObjectContext] deleteObject:product];
[delegate saveContext];
I also noted that i also receive the error for the following scenarios(debugging):
1.
[productList removeMProductObject:product];
[delegate saveContext];
2.
[[delegate managedObjectContext] deleteObject:product];
[delegate saveContext];
3.
[[delegate managedObjectContext] deleteObject:product];
[productList removeMProductObject:product];
[delegate saveContext];
4.
[[delegate managedObjectContext] deleteObject:product];
[delegate saveContext];//error
[productList removeMProductObject:product];
[delegate saveContext];
5.
[productList removeMProductObject:product];
[delegate saveContext];//error
[[delegate managedObjectContext] deleteObject:product];
[delegate saveContext];
At times, i may pass around either the productList or a product object (both of type NSManagedObject) to other controllers, always using assign in the property and never retain. productList is an object which may contain many product objects.
I am able to create new objects(NSManagedObject), but when it comes to deleting them i get the error mentioned above. When running the application in the simulator i keep a close eye on the sqlite file. after attempting to delete an object(code above), i notice the .sqlite file is removed.
I have created a few Core Data iphone applications with no problem but i seem to be having an issue here. i don't believe i am doing anything out of the ordinary but perhaps i am missing a small detail, but i don't know what!
Why is my .sqlite file being deleted and resulting in this error message on save?
How can i find a more useful error message so i can determine the cause of this error?
Found the problem, but i'm not sure why it fixes it:
Before i would delete/remove the object i would delete the image associated with it(stored in the document's directory).
The image by default has no value(nil), but i would attempt to delete it anyway:
NSError *deleteError = nil;
[[NSFileManager defaultManager] removeItemAtPath:[DOCUMENTS_DIRECTORY stringByAppendingPathComponent:product.mPictureName] error:&deleteError];
#ifdef DEBUG_MODE
if(deleteError) {
NSLog(#"Error deleting image: %#", [deleteError description]);
}
else {
NSLog(#"Image %# deleted.", product.mPictureName);
}
#endif
if i instead do a nil check before attempting to remove the image like this, i get no error when i delete the managed object itself:
NSString *imageName = product.mPictureName;
if(imageName) {
NSError *deleteError = nil;
[[NSFileManager defaultManager] removeItemAtPath:[DOCUMENTS_DIRECTORY stringByAppendingPathComponent:imageName] error:&deleteError];
#ifdef DEBUG_MODE
if(deleteError) {
NSLog(#"Error deleting image: %#", [deleteError description]);
}
else {
NSLog(#"Image %# deleted.", imageName);
}
#endif
}
...
//delete NSManagedObject and save
i guess attempting to access the product's picture when it is nil makes it all invalid.
I had a similar issue that's worth reporting for any poor shmo that gets caught up in this issue. In my case I had a table view controller that handled row deletions by deleting the corresponding data from the data model. When I used the remove##ObjectName##Object method (stubbed out by the auto-generated NSManagedObject subclass), I would get an error similar to the one you reported.
When I instead used NSManagedObjectContext: deleteObject I didn't have a problem saving the context. But, I observed an exception being thrown with no indication of reason from the console.
It turns out the tableView method canEditRowAtIndexPath: was being called for the row corresponding to the newly deleted object. It was clear my row count needed to be updated prior to the OS calling this method. But, I don't care. I simply check to see whether the size of my array of managed objects can be indexed by the indexPath row.
if([managedObjSet.objects count] > indexPath.row) {
ManagedObject* obj = [managedObjectArray objectAtIndex:indexPath.row];
return ([obj.anotherManagedObjSet count] ==0);
}

Handle row deletion in UITableViewController

I hava a UINavigationController. The first level is a UITableViewController, the second level just shows details on one of the items of the table view.
In this detail view, I can delete the item. It deletes the underlying managed object.
When I pop back to the view, I have a crash. I understand why, it's because I didn't update the cached array that contains the data.
I looked at several tutorials and I don't exactly understand how am I supposed to handle deletion. Maybe I don't understand exactly where I should fetch the objects in the model. Should I do a query for every cellForRowAtIndexPath and take the item in the result at position indexPath.row? It doesn't look efficient. Should I check for changes somewhere and recache the whole query in an array. I would think CoreData would provide something more natural but I couldn't find it so far.
Thanks in advance.
It is fairly simple. In the child view you should (really, really should) have a reference to the NSManagedObject you are working with. When you want to delete it then you just:
NSManagedObjectContext *moc = [[self myObject] managedObjectContext];
[moc deleteObject:[self myObject]];
NSError *error = nil;
if (![moc save:&error]) {
NSLog(#"Save failed: %#\n%#", [error localizedDescription], [error userInfo]);
}
This will delete the object. The parent, since it is using a NSFetchedResultsController (which you should also REALLY be doing) will take care of itself.
It seems somewhat non-standard deleting an item in the parent controller from your detail controller, but perhaps it makes sense in your case. I presume you know that you can directly delete items in the tableview. There are many example code projects from Apple which along with the docs should give you an idea how to do that.
To answer your question, you could create a property/variable in your detail controller's class which holds a reference to the tableview controller then send a message to that controller to handle the delete. Creating a protocol for this would be good style but not necessary. When the tableview class receives the delete item message, it updates the array, and when that view is redisplayed you should call reloadData on the tableview. This is the standard paradigm: make changes to your underlying data model and tell the tablview to reload.
In case you don't use NSFetchedResultsController, all you need to do is implement the following method:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object at the given index path.
NSManagedObject *rowToDelete = [currentRows objectAtIndex:indexPath.row];
[managedObjectContext deleteObject:rowToDelete];
// Commit the change.
NSError *error;
if (![managedObjectContext save:&error]) {
// Handle the error.
NSLog(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
}
else {
NSLog(#" %#", [error userInfo]);
}
}
// Update the array and table view.
[currentRows removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
}
}
currentRows is a NSArray of the objects you display in the table.
Cheers