I have two tableview controllers showing CoreData objects. One is a detail view (with sentences) one is an overview (with stories).
Pick a Story -> See the Sentences.
It looks like I was over-releasing my managedObjectContext; I originally released it in both TableViewControllers in dealloc and got a crash every third time I went between the two controllers (Story -> Sentence -> Story -> Sentence -> Story -> Crash).
Some debugging showed I was crashing in my App Delegate after this code in ViewDidLoad of both TableViewControllers:
if (managedObjectContext == nil)
{
managedObjectContext = [(StoryBotAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSLog(#"After managedObjectContext: %#", managedObjectContext);
}
Some more research found this discussion which led me to believe it was a case of an over released ManagedObjectContext:
The second more prosaic issue is simply an overreleased
NSManagedObject. Instruments ObjectAlloc tool should be able to help
you.
So I removed [managedObjectContext release]; from my dealloc in TableViewController and now I have no leaks (according to Instruments) and no crash.
It looks like the problem is fixed, but here's the question:
I might be missing the point altogether, and just hiding another issue. How can I find the over-release, or the real problem?
If I have fixed the problem, I'd like to know why it's fixed and why I don't need to release the MOC in the second TableViewController
MakeSentenceTableViewController.m
#implementation MakeSentenceTableViewController
#synthesize story, managedObjectContext;
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"My Story";
NSLog(#"Passed Story Object: %#", story);
if (managedObjectContext == nil)
{
NSLog(#"managedObjectContext == nil");
managedObjectContext = [(StoryBotAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSLog(#"After managedObjectContext: %#", managedObjectContext);
}else{
NSLog(#"managedObjectContext != nil");
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Sentence" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
//sorting stuff:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"order" ascending: YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
//[request setFetchBatchSize:FETCH_BATCH_SIZE];
[sortDescriptors release];
[sortDescriptor release];
fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request managedObjectContext:managedObjectContext
sectionNameKeyPath:nil cacheName:nil];
[request release];
NSError *error;
[fetchedResultsController performFetch:&error];
NSLog(#"FetchedResultsController: %#", fetchedResultsController);
NSLog(#"fetchedResultsController RetainCount at viewDidLoad: %d", [fetchedResultsController retainCount]);
}
//snip...table view bits
- (void)dealloc {
[fetchedResultsController release];
//Why don't I have to release this?
//[managedObjectContext release];
[super dealloc];
}
Because you are not retaining it. Even if the "MOC" property in you view controller is (retain), you are not calling the setter but simply setting the reference directly. If you want to retain and release it, you have to call self.managedObjectContext = ... instead (notice the dot), that is equivalent to [self setManagedObjectContext:...] and only then you can safely release it in the dealloc. Actually, since the "MOC" is owned by and managed in the app delegate, I wouldn't even bother to retain it.
Related
I've read several threads dealing with similar issues on here, but I just can't figure out what I am over-releasing. From a detail view controller for a Player object, I push a UITableViewController to select Location objects for that Player:
- (void)selectLocations
{
LocationSelectionController *vc = [[LocationSelectionController alloc] initWithStyle:UITableViewStyleGrouped];
vc.player = player;
[self.navigationController pushViewController:vc animated:YES];
[vc release];
}
Here is a look at some details of the LocationSelectionController:
- (void)saveContext {
NSManagedObjectContext *context = [player managedObjectContext];
if ([context hasChanges]) {
NSError *error = nil;
if (![context save:&error]) { //*** ERROR HERE ***
// show alert
}
}
}
- (void)viewDidLoad {
[super viewDidLoad];
// ....
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
// show alert
}
}
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSManagedObjectContext *context = [player managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Location" inManagedObjectContext:context];
[request setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFRC = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
aFRC.delegate = self;
self.fetchedResultsController = aFRC;
[request release];
[sortDescriptor release];
return _fetchedResultsController;
}
- (void)viewDidUnload {
[super viewDidUnload];
self.fetchedResultsController = nil;
}
- (void)dealloc {
self.fetchedResultsController = nil;
[_fetchedResultsController release];
[player release];
[super dealloc];
}
The functionality is always perfect the first time I navigate to the LocationSelectionController. I can interact with the Location objects with no problems at all. When I pop the view and return to the detail view of the Player object, again there is perfect functionality. It is only when I push the LocationSelectionController for a second time (even if it is from a different Player object) that there is a crash up attempting to save the context.
*** -[LocationSelectionController controllerWillChangeContent:]: message sent to deallocated instance 0x7026920
I've tried using instruments with NSZombie to find the problem, and it points me to an instance of LocationSelectionController.
Your handling of the NSFetchedResultsController is screwed up. First, consider this code:
NSFetchedResultsController *aFRC = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
aFRC.delegate = self;
self.fetchedResultsController = aFRC;
If your fetchedResultsController property is declared assign, this code is more or less correct (although then the semantics are wrong). If the property is declared retain, you're leaking a reference to aFRC here.
But either way, your deallocation is wrong. In dealloc, you have this:
self.fetchedResultsController = nil;
[_fetchedResultsController release];
The first line sets the property (and this presumably the _fetchedResultsController ivar) to nil, so the second line can never release the object. If the property is declared retain, I guess this is an attempt to clean up the leak mentioned above; if it is declared assign, this is evidence of the incorrect semantics mentioned above. And even if this worked "correctly", the version in viewDidUnload doesn't have that extra release so things are still wrong.
Either way, the leaked reference leaves the NSFetchedResultsController alive. Eventually it finds a change, which it tries to send to its delegate. And that, of course, fails because the delegate has long since been deallocated.
Chances are good that you never need to assign the fetchedResultsController from outside the implementation of this class. What you really should do then is something like this:
The property fetchedResultsController should be declared as:
#property (retain, readonly) NSFetchedResultsController *fetchedResultsController;
Your existing fetchedResultsController is fine, except that it should assign the _fetchedResultsController ivar directly instead of trying to assign self.fetchedResultsController.
Then the dealloc and viewDidUnload methods should each release the object something like this:
[_fetchedResultsController release];
_fetchedResultsController = nil;
You could also set _fetchedResultsController.delegate nil before releasing it, to be extra sure, but the documentation doesn't indicate this is necessary as it does for some other classes.
I had a similar problem with a different outcome.
In my case, I had some KVO observers in the controller that did not have corresponding removeObserver:forKeyPath: calls in dealloc.
I have two UITableViewControllers with a fairly simple ui flow. One UITableViewController loads another UITableViewController when you select an item in the first UITableViewController.
(UITableViewController) List of Stories -> Select a Story -> (UITableViewController) List of Sentences
In the second UITableViewController (MakeSentenceDetailViewController) I can't release my NSFetchedResultsController without causing an error (shown with Zombies set to on):
-[NSFetchRequest release]: message sent to deallocated instance 0x5b370f0
The retain count of the NSFetchedResultsController stays at 1 but when I try to release it in dealloc I get a crash.
The code, especially in regards to the NSFetchedResultsController is the same in both tableviews, but in the MakeSentenceDetailViewController I can't release this NSFetchedResults Controller with a crash - giving me a leak.
How can I safely release my NSFetchedResultsController? Why does it work fine in the parent (first) tableviewcontroller - but not in the second?
I can provide code for the first UITableViewController but in regards to NSFetchedResultsController it's declared and used in much the same way.
MakeSentenceTableViewController.h:
#interface MakeSentenceTableViewController : UITableViewController {
NSManagedObjectContext *managedObjectContext;
NSFetchedResultsController *fetchedResultsController;
}
#property (nonatomic, retain) Story *story;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#end
MakeSentenceTableViewController.m (relevant code with NSFetchedResultsController):
- (void)viewDidLoad {
[super viewDidLoad];
if (managedObjectContext == nil)
{
managedObjectContext = [(MyAppAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSLog(#"After managedObjectContext: %#", managedObjectContext);
}
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Sentence" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
//sorting stuff:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"order" ascending: YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
//[request setFetchBatchSize:FETCH_BATCH_SIZE];
[sortDescriptors release];
[sortDescriptor release];
fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request managedObjectContext:managedObjectContext
sectionNameKeyPath:nil cacheName:nil];
[request release];
NSError *error;
[fetchedResultsController performFetch:&error];
NSLog(#"FetchedResultsController: %#", fetchedResultsController);
NSLog(#"fetchedResultsController RetainCount at viewDidLoad: %d", [fetchedResultsController retainCount]);
}
- (void)dealloc {
//Gotta figure out why I can't release this:
[fetchedResultsController release]; //Crash! Burn!
NSLog(#"fetchedResultsController RetainCount at dealloc: %d", [fetchedResultsController retainCount]);
[managedObjectContext release];
[super dealloc];
}
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
// ...snip...
[request release];
You're releasing an object that you've relinquished ownership of (with -autorelease). You don't get an error at the point of the other release because the NSFetchedResultsController is also retaining the fetch request; thus the controller is the one actually causing the crash when it releases the last reference to the fetch request.
You are over-releasing the NSFetchRequest
You autorelease it here:
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
then release it again later:
[request release];
then later when you release the fetchedResultsController, it tries to release that same request again.
I'm a little stumped with an issue that I think goes back to my design.
I'm constructing a TableViewController based on a mainly static set of rows (4) - using that as a basis for a UITableView. Each row will kick off varying different views (detail, and UITableViews)... In my managed object context for the top view I can easily nav to the associated detail view because it's in context (nickname).. I initially thought about simply having unrelated tables and a series of buttons that would fire off the views... But I ditched that idea. My main current issue is knowing how to switch MOC or to different fetchedresults controllers (which are by the way defined in each .m to fetch from different tables.
At the moment, I've got a switch inside of TableViewControler.m.. This snipit works:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger switchval = indexPath.row;
switch (switchval) {
case 0: // Nickname
{
//This version brings up a detailed view controller -
NickNameDetail *controller = [[NickNameDetail alloc] initWithNibName:#"NickNameDetail" bundle:nil];
controller.fetchedResultsController = self.fetchedResultsController;
controller.managedObjectContext = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self.navigationController pushViewController:controller animated:YES];
break;
}
case 1:
{
//NextTableViewContoller *.... (etc)... here here
...
However, I can't figure out how to switch to a different context and get different rows.
meTableViewController.managedObjectContext = self.managedObjectContext;
nickNameDetail.managedObjectContext = self.managedObjectContext;
nextTableViewController.managedObjectContext = self.managedObjectContext;
Anyone run into a scenario like this before? Sorry, can't get the code formatter to behave.
Thanks!
In some cases it may be good/opportune to pass to the new view controller pushed on the navigation stack the MOC of your current view controller. However, you usually want to pass a newly created MOC. Do this as follows:
in your app delegate add the following method
- (NSManagedObjectContext*)createNewManagedObjectContext
{
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
return [moc autorelease];
}
in your view controller pass the MOC as follows
myAppDelegate *mainDelegate = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *mainMOC = [mainDelegate createNewManagedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:mainMOC];
newViewController.managedObjectContext = mainMOC;
and then handle the notification as needed, here is an example
- (void)contextDidSave:(NSNotification *)notification
{
[managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
[self.tableView reloadData];
}
You also need to define and use a different NSFetchedResultsController for each of your view controllers. This is because the data you want to fetch and display are, of course, different for each view controller (different entity, predicate etc). Simply define them in each implementation file. Here is an example:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Context" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
if(self.project){
// get the contexts for the project
NSPredicate * predicate = [NSPredicate predicateWithFormat: #"projectName == %#", self.project.name];
[request setPredicate:predicate];
}
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[request release];
[sortDescriptor release];
[sortDescriptors release];
return fetchedResultsController;
}
and then use the fetchedResultsController as needed. For instance, put this in your viewDidLoad method:
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Handle error
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
I'm trying to write an application with Core Data, and I have been able to successfully read and write to the core data database. However, if I write to the database in one view controller, my other view controllers will not see the change until the app is closed then reopened again. This is really frustrating. I'm not entirely sure how to get the refresh - (void)refreshObject:(NSManagedObject *)object mergeChanges:(BOOL)flag method to work. How do I get a reference to my managed object?
Anyways, here's the code I'm using to read the data back. This is in the viewDidLoad method.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Website" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"siteName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if(mutableFetchResults == nil) {
//Handle the error
}
[self setNewsTitlesArray:mutableFetchResults];
[mutableFetchResults release];
[request release];
[newsSourcesTableView reloadData];
Thanks for help in advance!
I am not entirely sure what do you intend to do from what I understand you are changing your managed object context in one view controller and you want the result to be visible in other view controllers is this correct?. Anyway a solution for this is to listen for the NSManagedObjectContextDidSaveNotification (which is send when the context is saved) and register as a observer the view controller you want to be afected by the changes:
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserver:myController selector:#selector(updateTable:) name:NSManagedObjectContextDidSaveNotification object:controller.context];
The updateTable in the myController Controller the selector could look something like this:
- (void)updateTable:(NSNotification *)saveNotification
{
if (fetchedResultsController == nil)
{
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
//Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
else
{
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
[self.tableView reloadData];
}
Hope that helps.
-Oscar
Actually, what I found was that I had the table set to load as my view. When I put the table into another view, everything worked fine.
I keep getting a -> Program received signal: “EXC_BAD_ACCESS”.
In the following code but I don't really understand why.
If I comment out the "executeFetchRequest" lines it goes away.
Shouldn't the [results release]; be all that's required?
Thanks in advance,
Matt
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
// fetch the delegate
TestingAppDelegate *app = (TestingAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = [app managedObjectContext];
// construct the request
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:#"Client" inManagedObjectContext:managedObjectContext]];
NSError *error;
NSArray *results = [managedObjectContext executeFetchRequest:request error:&error];
[results release];
}
I believe results, the result of executeFetchRequest:error:, should already be autoreleased. Because you are explicitly calling [results release], you're over-releasing that object when the current autorelease pool is drained. Remove the [results release] line and see if that fixes it.