iphone core data executeFetchRequest memory issue - iphone

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.

Related

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.

get NSString from NSFetchRequest issue

I'm using Core Data and I need to loop thru the result of the request, create several custom objects in the loop and store them in a NSMUtableArray, so I can send it to another view to feed a UI component. This is what I'm doing:
NSMutableArray *persons = [[NSMutableArray alloc] init];
NSError *error = nil;
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Person" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *info in fetchedObjects) {
ToggleButtonInfo *btn = [[ToggleButtonInfo alloc] init];
NSString *personName = [NSString stringWithFormat:#"ww %#", [info valueForKey:#"name"]];
NSLog(#"pn: %#", personName);
[btn setButtonInfo:personName];
[persons addObject:btn];
}
[fetchRequest release];
return persons;
The loop is working just fine, the information is there. The problem is that I get a "EXC_BAD_ACCESS" in my component if I use:
[info valueForKey:#"name"]
if I do something like this:
[btn setButtonInfo:#"something else here"];
everything works fine. So it looks like info is been de-allocated and that is causing the error, right? I try creating the scring using stringWithFormat but it doesn't work, same error.
An ideas?
Where do you get the EXC_BAD_ACCESS? I assume it's later when you're displaying the button? -setButtonInfo: probably isn't retaining, or you're over-releasing somewhere else.
Note that you're leaking btn in this code.

Can't Release NSFetchedResultsController 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.

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.

iPhone Objective-C: Exact same method call, working the first time, but getting "unrecognized selector" the second

I am working with a UITableViewController. The method that fails is:
- (void) testLoc {
self.dictionary = [self.delegate getLocationsWithLatitude:#"0.0" longitude:#"0.0"]; //HERE
[self setUpTableArray:dictionary];
}
Test results have shown that the exception is thrown in the first line of testLoc.
- (NSDictionary *) getLocationsWithLatitude:(NSString *)latitude longitude:(NSString *)longitude;
All I do in the above method is make an HTTP request and return an NSDictionary.
Below is my viewDidLoad (the first method call, which works) for the UITableViewController:
- (void)viewDidLoad {
self.delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[self testLoc];
[super viewDidLoad];
}
And here is my viewWillAppear, which I get "unrecognized selector" from:
- (void)viewWillAppear:(BOOL)animated {
self.delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[self testLoc];
[super viewWillAppear:animated];
}
Here is the error that I am getting:
-[NSCFString key]: unrecognized selector sent to instance 0x141770
The reason I am doing this is because I have a table view that I want to update every time I tab back to it with a tab bar.
This is for the first real app that I am building, and I would really appreciate any kind of insight. Thanks!!
UPDATE
Here is getLocationsWithLatitude:
- (NSDictionary *) getLocationsWithLatitude:(NSString *)latitude longitude:(NSString *)longitude {
OAMutableURLRequest *locationsRequest = [[OAMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://somerequesturl"]
consumer:self.globals.consumer
token:self.globals.requestToken
realm:nil signatureProvider:nil];
[locationsRequest setHTTPMethod:#"GET"];
[locationsRequest setParameters:[NSArray arrayWithObjects:
[OARequestParameter requestParameter:#"geolat" value:latitude],
[OARequestParameter requestParameter:#"geolong" value:longitude],
nil]];
[locationsRequest prepare];
NSData *locationsData = [NSURLConnection sendSynchronousRequest:locationsRequest returningResponse:nil error:nil];
NSString *locationsString = [[[NSString alloc] initWithData:locationsData encoding:NSUTF8StringEncoding] autorelease];
[locationsRequest release];
SBJSON *jsonParser = [SBJSON new];
NSError *error = nil;
return [jsonParser objectWithString:locationsString error:&error];
}
Here is setUpTableArray:
- (void) setUpTableArray:(NSDictionary *)dict {
NSArray *array = [dict objectForKey:#"groups"];
if (array != nil) {
self.placesArray = array;
}
}
What happens if you change viewWillAppear like this:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self testLoc];
}
Wow, I definitely found my own problem this time. Just get rid of [locationsRequest prepare]; I definitely saw code somewhere that has that, but this tells me otherwise.
EDIT
Turns out this was a memory-memory allocation error. Getting rid of that line wasn't the actual fix. It seems to be some problem with a string being auto-released. I asked a follow up question here.