how to remove non committed objects from context? - iphone

I'm using core data and NSFetchedResultsController in an app, for feeding a UIViewTable with Car objects. I have a button that, when tabbed, takes the app to another view when the user can add a new Car. The problem I have is that even if the user doesn't create the car in the second view, it's added to the table. If I restart the application, the Car wasn't added to the DB.
This is related to the fact that I create an instance of the Car in the second view, in the viewDidLoad method, using something like this:
car = [NSEntityDescription insertNewObjectForEntityForName:#"Car"
inManagedObjectContext:context];
This is added even if I don't save the context.
I tried to delete the object when the second view is about to be closed, using this:
[context deleteObject:car];
This partially works. The car is not added to the table in the first page, but looks like the indexes of the data source are altered. If I scroll all the way down I got this error:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[_PFBatchFaultingArray objectAtIndex:]: index (9) beyond bounds (9)'
Any ideas will be appreciated.

Try creating your car object by passing a nil context:
car = [NSEntityDescription insertNewObjectForEntityForName:#"Car"
inManagedObjectContext:nil];
From there, if the user decides to save it you can move the object to the main context.
The other alternative is to have a separate MOC for this view (which adds unnecessary complications) and then again move the object between contexts if/when required.
Cheers,
Rog
PS: if you're wondering whether you can pass nil as the managedObjectContext, this is straight from Apple's docs on NSManagedObject:
...If context is not nil, this method
invokes [context insertObject:self]
(which causes awakeFromInsert to be
invoked)...
More details here
[EDIT]
In addition to this, I just came across something interesting when looking at the NSFetchRequest documentation and thought you might want to have a go (I haven't tried myself). It appear that you can tell the fetchRequest whether to include pending changes (i.e. not saved) or not when fetching objects:
- (void)setIncludesPendingChanges:(BOOL)yesNo
FYI the default value is YES - more details here

First, try delaying the insertion of the new object when the user commits the change.
Only if it's not appropriate:
When the context has changed, NSFetchedResultsController is automatically informed to modify its data passed to the table view controller. However, the table view itself is not modified, so you have to update the table view by yourself.
That's why NSFetchedResultsController has a delegate object (conforming to NSFetchedResultsControllerDelegate protocol) which is responsible to update the table view.
For example, in CoreDataBooks sample project, you will see delegate methods of this protocol in RootViewController class. The most related delegate method is controller: didChangeObject: atIndexPath: forChangeType: newIndexPath:. You may probably want to do something like the following:
if (changeType == NSFetchedResultsChangeDelete) {
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

Related

Is there a way to instantiate a NSManagedObject without inserting it?

I have a user interface to insert a Transaction. once the user clicks on a plus he gets the screen and i want to instantiate my Core Data NSManagedObject entity let the user work on it. Then when the user clicks on the Save button i will call the save function.
so down to code:
transaction = (Transaction *)[NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:self.managedObjectContext];
//even if i dont call save: its going to show up on my table
[self.managedObjectContext save:&error]
P.S i am using an NSFetchedResultsController on that table and I see that the NSFetchedResultsController is inserting a section and an object to the table.
My thought is if there is a way to instantiate the Transaction NSManagedObject i could update it with out saving untill the client choses to.
For what it's worth, Marcus Zarra seems to be promoting the nil context approach, claiming that it's expensive to create a new context. For more details, see this answer to a similar question.
Update
I'm currently using the nil context approach and have encountered something that might be of interest to others. To create a managed object without a context, you use the initWithEntity:insertIntoManagedObjectContext: method of NSManagedObject. According to Apple's documentation for this method:
If context is not nil, this method
invokes [context insertObject:self]
(which causes awakeFromInsert to be
invoked).
The implication here is important. Using a nil context when creating a managed object will prevent insertObject: from being called and therefore prevent awakeFromInsert from being called. Consequently, any object initialization or setting of default property values done in awakeFromInsert will not happen automatically when using a nil context.
Bottom line: When using a managed object without a context, awakeFromInsert will not be called automatically and you may need extra code to compensate.
here is how i worked it out:
On load, where we know we are dealing with a new transaction, i created an out of context one.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Transaction" inManagedObjectContext:self.managedObjectContext];
transaction = (Transaction *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
then when it came to establishing a relation ship i did this:
if( transaction.managedObjectContext == nil){
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Category" inManagedObjectContext:self.managedObjectContext];
Category *category = (Category *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
category.title = ((Category *)obj).title;
transaction.category = category;
[category release];
}
else {
transaction.category = (Category *)obj;
}
and at the end to save:
if (transaction.managedObjectContext == nil) {
[self.managedObjectContext insertObject:transaction.category];
[self.managedObjectContext insertObject:transaction];
}
//NSLog(#"\n saving transaction\n%#", self.transaction);
NSError *error;
if (![self.managedObjectContext save:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
There's a fundamental problem with using a nil MOC: Objects in different MOCs aren't supposed to reference each other — this presumably also applies when one side of a relationship has a nil MOC. What happens if you save? (What happens when another part of your app saves?)
If your object doesn't have relationships, then there are plenty of things you can do (like NSCoding).
You might be able to use -[NSManagedObject isInserted] in NSPredicate (presumably it's YES between inserting and successfully saving). Alternatively, you can use a transient property with the same behaviour (set it to YES in awakeFromInsert and NO in willSave). Both of these may be problematic if a different part of your app saves.
Using a second MOC is how CoreData is "supposed" to be used, though; it handles conflict detection and resolution for you automatically. Of course, you don't want to create a new MOC each time there's a change; it might be vaguely sensible to have one MOC for unsaved changes by the slow "user thread" if you don't mind some parts of the UI seeing unsaved changes in other parts (the overhead of inter-MOC communication is negligible).
You can insert an NSManagedObjectContext with the -[NSManagedObject initWithEntity:insertIntoManagedObjectContext:], passing nil for the managed object context. You must, of course, assign it to a context (using -[NSManageObjectContext insertObject:] before saving. This is, as far as I know, not really the intended pattern in Core Data, however (but see #mzarra's answer here). There are some tricky ordering issues (i.e. making sure the instance gets assigned to a context before it expects to have one, etc.). The more standard pattern is to create a new managed object context and insert your new object into that context. When the user saves, save the context, and handle the NSManagedObjectDidSaveNotification to merge the changes into your 'main' context. If the user cancels the transaction, you just blow away the context and go on with your business.
An NSManagedObject can be created using the nil as the context, but if there other NSManagedObjects it must link to it will result in an error. The way I do it I pass the context into the destination screen and create a NSManagedObject in that screen. Make all the changes link other NSManagedObjects. If the user taps the cancel button I delete the NSManagedObject and save the context. If the user taps the the save button I update the data in the NSManagedObject, save it to the context, and release the screen. In the source screen I update the table with a reload.
Deleting the NSManagedObject in the destination screen gives core data time to update the file. This is usually enough time for you not to see the change in the tableview. In the iPhone Calendar app you have a delay from the time it saves to the time it shows up in the tableview. This could be considered a good thing from a UI stand point that your user will focus on the row that was just added. I hope this helps.
transaction = (Transaction *)[NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:nil];
if the last param is nil, it will return a NSManagedObject without save to db

One UITableViewController with many NSFetchedResultsControllers - bad idea?

My app involves a main screen with several sorting/viewing options of a set of data. Depending on what the user chooses, I may list them, e.g. alphabetically, N most recent, or grouped somehow.
I started the app as a Core Data Table-based navigation app; my app delegate sets up the Core Data stack (unchanged generated code), gives the NSManagedObjectContext to the controller for the initial screen, and it passes it to the UITableViewController implementing my "list of entities".
Since my three different views of the same data all end up showing a table listing out the data, I expanded this class to have three different NSFetchedResultsControllers, each with the one UITableViewController instance as their delegate. Before pushing this view controller on the stack, I call a method to switch which NSFetchedResultsController to use, e.g.
-(void)configureForMostRecent {
self.activeFetchedResultsController = self.mostRecentResultsController;
}
Now I am getting random crashes from Core Data, e.g. NSInternalInconsistencyException and other things like that. Sometimes, I use the app and everything's fine, other times, it crashes almost instantly.
So, my instinct is that my design is just a Bad Idea(tm).
Should I basically stick to a "One UITableViewController to one NSFetchedResultsController" sort of model and just use other coding styles to reduce boilerplate?
Using multiple NSFetchedResultsController instances is a perfectly valid design based on the description you have given so far.
Are you trying to use the same cache for each of these NSFetchedResultsController instances? Are you calling -reloadData on the table whenever you switch to a different NSFetchedResultsController? Both of those could be causing the crash you are seeing.
Update
The delegate is not an issue but not calling -reloadData is going to be a killer. The delegate methods really are there just to update the UITableView when the NSFetchedResultsController changes. The fact that a reference to is passed into those delegate methods is a hint that they are designed to handle multiple NSFetchedResultController` instances calling into them.
You could use one fetch controller, adjusting the fetch predicate and refetching as needed.
EDIT
Following my example case:
[NSFetchedResultsController deleteCacheWithName:#"MyObjectsCache"];
NSPredicate *_predicate = nil;
if (condition) {
_predicate = [NSPredicate predicateWithFormat:mySearchPredicateString];
self.currentTableView = searchDisplayController.searchResultsTableView;
}
else {
_predicate = [NSPredicate predicateWithFormat:myDefaultPredicateString];
self.currentTableView = tableView;
}
[fetchedResultsController.fetchRequest setPredicate:_predicate];
NSError *_error = nil;
if (![fetchedResultsController performFetch:&_error]) {
// handle error
}

tableview coredata temporary object

I am using a tableview with data from coredata using nsfetchedresultscontroller. When the view loads i make a new entity using
SomeManagedObject *someManagedObject = [NSEntityDescription insertNewObjectForEntityForName:#"SomeManagedObject" inManagedObjectContext:self.managedObjectContext];
This way the new entity appears in my tableview. Now i want this entity to be only temporary, but when i edit some object inside the tableview and save the managedObjectContext the temporary entity will also get saved and i don't want that.
Is their a way to save one object only and not everything inside de managedObjectContext?
Is their some other way to make a temporary object for my tableview.
Any help would be very welcome.
Thanks
Ton
Create the new NSManagedObject with it's alloc init and pass nil instead of the NSManagedObjectContext. Then if you later decide you want that object to be permanent then set it's context. However this will not allow you to see it in a NSFetchedResultsController because it will not be associated with the context.
A better answer can be provided if you could explain what your ultimate goal is.
No, in a managedObjectContext saving is a all or nothing. What I do not know is what happens if you set the persistent store of the managed object to nil
- (void)assignObject:(id)object toPersistentStore:(NSPersistentStore *)store
If you then save the managedObjectContext this object should not be saved. It is just a guess, but tell me if it works ;-)
For temporary managed objects, create them with a 2nd managed object context (MOC). When you are finished, simply release the MOC without performing a save.
Look at the Adding a Book code in CoreDataBooks which uses the same approach to throw away the newly added object when the user cancels.

Core Data - How to check if a managed object's properties have been deallocated?

I've created a program that uses core data and it works beautifully.
I've since attempted to move all my core data methods calls and fetch routines into a class that is self contained. My main program then instantiates that class and makes some basic method calls into that class, and the class then does all the core data stuff behind the scenes. What I'm running into, is that sometimes I'll find that when I grab a managed object from the context, I'll have a valid object, but its properties have been deallocated, and I'll cause a crash. I've played with the zombies and looked for memory leaks, and what I have gathered is it seems that the run loop is probably responsible for deallocating the memory, but I'm not sure.
Is there a way to determine if that memory has been deallocated and force the core data to get it back if I need to access it? My managedObjectContext never gets deallocated, and the fetchedResultsController never does, either.
I thought maybe I needed to use the [managedObjectContext refreshObject:mergeData:] method, or the [managedObjectContext setRetainsRegisteredObjects:] method. Although, I'm under the impression that last one may not be the best bet since it will be more memory intensive (from what I understand).
These errors only popped up when I moved the core data calls into another class file, and they are random when they show up.
Any insight would be appreciated.
-Ryan
Sounds to me like you are not retaining objects you want to keep hanging around. If you are doing something like this:
NSArray *array = [moc executeFetchRequest:request error:&error];
you do not own the returned array and it will most likely disappear when the current autorelease pool is drained. This will occur when the run loop finishes processing the current event.
All this is speculation. If you want a proper answer, you need to post your code.
It's hard to know what the problem is based on your description, but you might want to look at the Core Data memory management guide. You shouldn't have to worry about memory management for managed objects and their entities (they're fetched and faulted automatically). When you talk about "properties," do you mean custom properties backed by ivars? If so, these should be released in didTurnIntoFault and allocd as needed (probably in the accessor).
I was struggling with a similar issue. I'm using a managed object class and want to set its properties dependent on user input. But the sometimes the properties and sometimes the whole managed object were deallocated.
After reading the Apple documentation http://developer.apple.com/library/IOs/#documentation/Cocoa/Conceptual/CoreData/Articles/cdMemory.html the chapter "The Role of the Managed Object Context" I learned that managed objects are released each run loop completes.
And there is the golden advice to set
[myMangedObjectContext setRetainsRegisteredObjects:YES];
(I had to set it in the init method (initWithNibName for me) of my view controller.)
You should also regard to retain only the objects you need to as explained in the documentation. But read it yourself.
If I'm not right please correct me.
I also made a class that handles all my CoreData fetching and stuff. I ran into a couple of gotcha's, so here are some tips. (If I am making any memory management errors in these examples, please let me know.)
Two things:
1) Made a "fetchFiredObject" method in the CoreData handler class. So when I want to get a managedObject that has all its variables and is a "fully feathered bird" so to speak, instead of doing:
aManagedObject *myManagedObject = [myCoreDataHandler.managedObjectStorageArray objectAtIndex:1];
int x = myManagedObject.someVariable.intValue;
instead I do:
aManagedObject *myManagedObject = [myCoreDataHandler fetchFiredObjectAtIndex:1];
int x = myManagedObject.someVariable.intValue;
And in myCoreDataHandler's fetchFiredObjectAtIndex:i method, we're going into the array, finding the object key at index i, then doing a fetchRequest for that object key, and returning the freshly-fetched managedObject so that it won't have been faulted or deallocated, etc. :D
2) When I create a new child viewController, I populate its "myCoreDataHandler" value from the parent upon creation. However, this happens on a subsequent line of code after the line of code that creates the new viewController. Therefore, any code in the child's viewDidLoad that tries to use myCoreDataHandler's methods will return empty objects because viewDidLoad completes before the parent's next line of code where it sets the values of globals in the child object. So make sure you are not accessing your "Core Data handling object" from within viewDidLoad or anything local methods called by viewDidLoad! Instead call them from the parent after creating the new viewController.

Is this a tableView issue or a CoreData Issue

I have a CoreData-driven navigation app and I'm trying to figure out why It's crashing.
I've got a hierarchy which is 3 view Controllers deep, all related by coredata relatioships, like this.
TableViewA =relationship= TableViewB =relationship= TableViewC
I'm honestly a novice at core data and I think my problem lies in the fetched results controller. I have one in TableViewA and another in TableViewB, and no matter how deep I go, the console always cites TableViewB's fetched results controller methods after a crash. Is this the problem?
What's happening specifically is if I launch my app and drill down into the hierarchy of one record, let's call it Record1, I can delete sub records to my hearts content. Gone! no problem!
But the second I go back to TableViewA and drill down into a different record, let's call that one Record2, and try to delete it's subrecords my app crashes, with the console citing this code from TableViewB as the problem.
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[self.tableView beginUpdates];
}
When I go into the debugger, the specific method it always has a problem with is:
if (![x.managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
Just a confirmation of my idiocy with CoreData is all I'm looking for I think.
Oh and how many ManagedObjectContexts should I have in an app of this type. I've been told I should have separate ones for adding content, which then should re-integrate into the main one. Is this true?
Thanks!
I feel like this is a problem where you delete subrecords for Record1, then some inconsistency occurs when you go to delete the subrecords for Record2 - the original managed object context doesn't get saved, or gets into a conflict, or some such. I'd definitely check into your Core Data object management before your table view code.
As for having multiple managed object contexts, usually you only have an additional context when you're adding entirely new records. The typical pattern is to add new records into a secondary context, then merge that context into your app's primary context once the new record is added and saved. For modification or deletion, just use the original context.
I could be more definite about the problem you're having if you post the logging output of the NSLog statement in your last code snippet. The error's domain, code, and userInfo attributes will all be tremendously helpful here.