Can't Release NSFetchedResultsController in dealloc - iphone

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.

Related

Core Data Memory Leaks

I began testing my app in Instruments to clean up any memory leaks. I've been able to clear up all the memory leaks except those related to Core Data. Instruments always points me to this section of code:
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error
}
I declare the managedObjectContext in my header file with the following code:
#interface UpperBody : UITableViewController <UITableViewDelegate, UITableViewDataSource> {
IBOutlet UITableView *upperTable;
NSMutableArray *exercises;
NSManagedObjectContext *managedObjectContext;
}
#property (nonatomic, retain) NSMutableArray *exercises;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
I release the managedObjectContext in the (void)dealloc section. Here is the full section of code using the managedObjectContext:
- (void)loadExercises {
if (managedObjectContext == nil) {
managedObjectContext = [(iFitAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Exercises" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"category == 1"];
[request setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"exerciseName" ascending:YES];
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 setExercises:mutableFetchResults];
// [exercises addObject:#"Add Exercise"];
NSLog(#"Count of exercises %i", exercises.count);
[mutableFetchResults release];
[request release];
[self.tableView reloadData];
}
Any advise on what might be causing the leaks would be greatly appreciated! Thank you in advance!
kThere was probably a leak in the code I had above. I got around declaring an NSManagedObjectContext by just using a pointer to one whenever needed. Here is a sample of the code:
iFitAppDelegate *appDelegate = (iFitAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
This fixed my leak, so it must have had to do with how I was allocated and releasing the NSManagedObjectContext. Thank you for the pointers, #albertamg!

Error on second save of NSManagedObjectContext (deallocated insance)

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.

Why don't I have to release managedObjectContext in the 2nd TableViewController

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.

Example of how to make a data factory for core data access in cocoa (iPhone)?

I have been slowly learning iPhone development and seem to keep hitting walls where I can't figure out how to do what I want to do the right way :(
Basically, I want a class that handles all interactions with the data layer, for example, getting a mutable array of some list of objects from the data store.
This is pretty trivial in other languages where you have a garbage collector, but in Objective-C on the iPhone, I'm not sure what to do.
This is an example method on a DataFactory class we were creating. Note the comment on where we are not sure when to release....
- (NSMutableArray*)fetchAllDrivers{
NSMutableArray *results = [[NSMutableArray alloc] init];;
if (self.appDelegate != nil) {
NSManagedObjectContext *context = [self.appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
[request setEntity: entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor, nil];
[request setSortDescriptors: sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSError *error;
results = [[context executeFetchRequest:request error:&error] mutableCopy];
if (results == nil) {
//something went wrong
}
//Where should this be released??? Certainly not here!
[results release];
[request release];
}
else {
[NSException raise:#"Can't fetch b/c app delgate is nil!" format: #"!!!"];
}
return results;
}
Calling code, related to my comment:
NSMutableArray* arr = [dataFactory fetchAllDrivers];
[arr retain];
//Some code where we use arr
[arr release];
Following naming conventions, your fetchAllDrivers should return an autoreleased object.
- (NSMutableArray*)fetchAllDrivers
{
if (!self.appDelegate) {
// Big Problems Raise exception immediately if you want...
return nil;
}
NSManagedObjectContext *context = [self.appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
[request setEntity: entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor, nil];
[request setSortDescriptors: sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSError *error = nil;
NSMutableArray *results = [[NSMutableArray alloc] initWithArray:[context executeFetchRequest:request error:&error] copyItems:YES];
if (error) {
// Something went wrong
[results release];
// Error handling code here
[request release];
return nil;
}
[request release];
return [results autorelease];
}
NSMutableArray* arr = [dataFactory fetchAllDrivers];
[arr retain];
//Some code where we use arr
[arr release];
By convention, any object returned from the method of an external object is autoreleased. You don't need to retain them except in properties. If you only using arr in the local scope of the method then you don't need to retain/release it. It is autoreleased and will die after the end of the local scope.
If you need to have arr hang around inside the object. You should store it in a retained property:
#property (nonatomic,retain) NSMutableArray *arr;
... then use it with the self notation to ensure retention:
self.arr=[dataFactory fetchAllDrivers];
... then you need only release it in the class' dealloc method.
Having one object manage your data model is very good idea but it is not a "factory". Objective-c does not use factories like C++ and similar languages. Trying to think in those terms will lead to grief. The object should instead be thought of as a "controller" or "manager".

iphone core data executeFetchRequest memory issue

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.