Dealloc Being Called Twice? - objective-c

Resloved!
Thanks to Lone Gunman, this issue was due to an oversight of not setting the many delegates to nil before releasing them.
This is a strange one... I'm familiar with basic memory management but I think something is unusual about what I am seeing. Here is a little background...
I have a NavigationController that handles the navigation between the following ViewControllers:
Home -> Games -> Game
When running the code it falls down when leaving the Game. Within the GameViewController there is a dealloc method that resembles:
- (void)dealloc
{
[board release];
[opponentsViewController release];
[instructionsViewController release];
[imgPicker release];
[gameView release];
[super dealloc];
}
When the navigation controller goes back to the Games list (from the Game) it throws a EXC_BAD_ACCESS. So I bring up my trusty profiler and check for Zombies. Alas, just as I expected a message is being sent to a deallocated object! Digging deeper I find there to be 3 entries in the object's history:
Board getting alloc'd (called by Game's init method)
Board getting released (called by Game's dealloc method)
Board being Zombie'd (called by Game's dealloc method)
Both calls 2 and 3 are called from UINavigationController setDisappearingViewController.
In my dealloc method I set breakpoints to each release call, when doing so - the [board release] call occurs, then the [opponentsViewController release] call occurs then the [board release] call occurs again. So I'm seeing the dealloc method does not finish completely and calls again.
What might be causing this?
Edit: This is the GameViewController Implementation
Code from the Games controller that adds this game:
-(void) gotoGame:(int)tag {
game = [[GameViewController alloc] init];
[self.navigationController pushViewController:game animated:YES];
[game release];
}
Edit: This is the GameViewController Header

I would try setting all your ivar's delegates to nil (EDIT: in dealloc). I've had a similar problem with a fetched results controller. Failed to set the its delegate to nil in dealloc and the core data stack still had a pointer to it when the view controller was released.
So that's my bet, set ivar delegates to nil in dealloc, although I can't see your header to know what protocols your are conforming to be sure.
EDIT: Explanation
Setting a delegate is actually giving the object that is doing the delegation a pointer (I believe it usually an assigned property).
#property (assign) delegate;
I'll use the problem I had as an example.
So let's say you have a view controller that has a fetchedResultsController as an ivar. When you set the FRCs delegate:
fetchedResultsController.delegate = self;
and the view controller gets released, any object that is using that pointer still thinks it's live. You would think since the FRC is getting released in dealloc as well, you'd be fine(which is why it took me 4 days to figure this out :) ), but sometimes other parts of an implementation use your delegate as well. So the fix is:
-(void)dealloc
{
self.fetchedResultsController.delegate = nil;
[_fetchedResultsController release];
[super dealloc];
}
Note: as soon as the new tools are available to everyone you won't have to worry about this stuff anymore ^ ^;

try
- (void) dealloc {
if(game != nil){
//release here
[game release];
}
[super dealloc];
}
By the way it seems you have declare game in header file and just after pushing you are releasing it and also in dealloc method you are releasing it. Either remove the release call from dealloc method or change you method like this.
-(void) gotoGame:(int)tag {
GameViewController *game = [[GameViewController alloc] init];
[self.navigationController pushViewController:game animated:YES];
[game release];
}
UPDATE
Also you are not using the tag anywhere. Why don't you create your init method like this
GameViewController *game = [[GameViewController alloc] initWithTag:tag];
[self.navigationController pushViewController:game animated:YES];
[game release];

Related

Not calling [super dealloc] inside a static UIViewController subclass

In my app I have a UIViewController subclass (VC for short) that I only use in one place in the entire app. In that place, I have been creating and pushing it like this:
MyViewController* VC = [MyViewController new];
[self.navigationController pushViewController:VC animated:YES];
[VC release];
but I was thinking that since this is the only place I am using a view controller of this type, I could do something like this so the settings used won't be reset each time the view controller is pushed onto the stack:
static MapsToSendPicker* VC = nil;
if(!VC) {
VC = [MapsToSendPicker new];
}
[self.navigationController pushViewController:VC animated:YES];
[VC release];
The problem with that code is that in VC's dealloc method, I release all of my instance variables and set them to nil, and finally I call [super dealloc]. This deallocates the static view controller, but the test if(!VC) isn't evaluated to true after (this would defeat the purpose of the whole idea if it were; then I'd have to recreate the view controller each time anyway).
My solution is overriding the dealloc method in MyViewController and not calling [super dealloc] at the end. This works, but the compiler raises a warning. How can I get rid of that warning while maintaining the functionality I gain with this design? Thanks!
Edit:
After a quick Google search, I have found this solution:
- (void)dealloc {
if(NO) {
[super dealloc];
}
}
but I would like something a little bit... cleaner. Any thoughts?
Remove the [VC release]; line and add [super dealloc] back. Everything will work properly and dealloc will never get called. Generally you should consider using NSUserDefaults in order to restore the VC properties instead of keeping the controller in memory all the time.

What things should I look for that could be increasing my retain count in a UIViewController which doesn't dealloc

I have a UIViewController that is popped from the navigation stack on iPhone and removed by setRootViewController on the iPad.
In both cases the UIViewController fails to dealloc, which means that something is hanging on to it; this is confirmed by logging [self retainCount] which is two, right before the pop or the setRootViewController (for some reason it's four in ViewWillDisappear).
The UIView has audio (using Flite Text to Speech - which uses AVFoundation.framework) and animation.
What things should I look for that could increasing my retain count in the view controller and stopping the view controller from being politely dealloced as it should be.
Here's how the view is pushed onto the view stack or set as the RootViewController;
-(IBAction)pushShare:(id)sender{
ShareViewController *shareViewController = [[ShareViewController alloc] initWithNibName:#"ShareViewController" bundle:nil];
NSLog(#"1. SVC Retain count %d", [shareViewController retainCount]);
[shareViewController setParentIsIpadMake:YES];
NSLog(#"2. SVC Retain count %d", [shareViewController retainCount]);
[shareViewController setStory:story];
NSLog(#"3. SVC Retain count %d", [shareViewController retainCount]);
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
StoryBotAppDelegate *appDelegate = (StoryBotAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.window setRootViewController:shareViewController];
NSLog(#"4. SVC Retain count %d", [shareViewController retainCount]);
} else{
[self.navigationController pushViewController:shareViewController animated:YES];
NSLog(#"4. SVC Retain count %d", [shareViewController retainCount]);
}
NSLog(#"releasing Svc...");
[shareViewController release];
NSLog(#"5. SVC Retain count %d", [shareViewController retainCount]);
}
First, try running Xcode's static analyser, it may find what is wrong.
You can also try overriding retain method with calling [super retain] and logging. Moreover you can put a breakpoint there and look at the stack when Xcode stops on it.
Or you can run ARC conversion tool and see what happens.
Retain count method is next to useless. The system libraries will be retaining your VC and releasing it. Which is why sometimes it will be a strangely high number. There are lots of topics on this.
Don't be concerned about the view animation delegate having the VC. The view and the VC are intimately tied together and released together, and the view doesn't retain its animation delegate - it just assigns it as a reference.
If u are testing in the simulator, force a memory warning and see if it is dealloc'ed. Dealloc'ed isn't always called immediately when you pop the VC.
Run instruments leaks. Does it say the VC is leaking memory? Run instruments with Zombies to find out whe it is leaking.
I read this question about NSTimer stopping the dealloc of a UIView. Turns out I had a NSTimer firing my animations which was stopping dealloc from happening nicely. I fixed it by adding a method called killTimer:
-(void)killTimer{
[animationTimer invalidate];
animationTimer = nil;
}
which I called on closing the view.
Thanks heaps for the answers to this question though - they were super helpful. I didn't actually know about the static analyzer until asking - or about how useless retain counts are; so +1 to you both.

Objective C - iOS - Dealloc is being called in middle of execution of webViewDidFinishLoad

I am having an issue with memory management in ios. The problem is when I push a viewController which has a webView on to the navigation stack and when I click back before the webview is loaded I am getting exec_bad_access.
In 'Class A' I am creating a NewViewController, then I am pushing it on to the navigation stack, and then releasing it. So here I am giving away my ownership as I am releasing it.
Class A:
-(void)onButtonClick{
NewViewController* viewController = [[NewViewController alloc] init];
[self.navigationController pushViewController: viewController........];
[viewController release];
}
Class B has a webView and a timer in it and implements UIWebViewDelegate. So, in here when the webView shouldStartLoad I am starting the timer. And then when it is done loading I am invalidating it.
Class B:
#interface NewViewController : UIViewController <UIWebViewDelegate>
NSTimer* timer
......
#property(nonatomic, retain) IBOutlet UIWebView* webView;
#end
#implementation
-(void)viewDidLoad{
[super viewDidLoad];
[webView loadRequest:someRequest];
}
.....
.....
-(void)dealloc{
[self makeTimerNil];
[self.webView stoploading];
self.webView.delegate = nil;
[self.webView release];
self.webView = nil;
.....
[super dealloc];
}
-(void)resetTimer{
[self makeTimerNil];
//timer will retain target - self
timer = [NSTimer scheduledTimerWithTimeInterval:kNetworkTimeOut target:self selector:#selector(networkTimedOut) userInfo:nil repeats:NO];
}
-(void)makeTimerNil{
if([timer isValid]){
[timer invalidate];
timer = nil;
}
}
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
[self resetTimer];
......
return YES;
}
-(void)webViewDidFinishLoad:(UIWebView *)webView{
//NO Exception. Can access self
[self anotherMethod];
//timer releases retained target -self
[self makeTimerNil];
//Exception self has been deallocated
[self anotherMethod];
}
#end
But the issue is when the webView is loading if I click back button on the navigation bar, the newViewController is getting deallocated which is fine. But this is happening in the middle of execution of webViewDidFinishLoad. Why is dealloc being called in the middle of execution of webViewDidFinishLoad? Don't they run on the same thread (Main - UI Thread) ?
Any ideas on how to fix the issue?
Your problem is absolutely the timer. According to the NSTimer documentation, an active timer holds a retain on its target object. As a result, your controller cannot get dealloc'd while the timer is active. That in itself is a bug in your architecture, since from your -dealloc method it's obvious you're expecting the view controller to be dealloc'd while the timer is active. But in the case of the webview, it's causing another problem. Specifically, in the middle of your -webViewDidFinishLoad: method you're canceling your timer. This causes it to release its target, and since it was the only owner of your view controller, the view controller immediately deallocs.
dealloc will be called when the retain count drops to 0 and will happen immediately, not later on. It is happening on the thread that the count drops to 0 in, in your case the thread that webViewDidFinishLoad: is called in.
One this you could do here is to add a [self retain] at the top of your webViewDidFinishLoad: method and a [self release] at the bottom of it. However I'm not 100% sure if that method is guaranteed to run on the main thread and if a view controller gets dealloc'ed on a thread other than the main thread then there can be lots of problems.
To fix this really, I would override viewDidDisappear: and do all the tearing down of the web view there (i.e. setting the delegate to nil and stopping the timer, etc). That way it'll happen on the main thread and at the point you tap the back button.
You are correctly nil'ing out the web view's delegate in -dealloc.
I think (but this might be wrong) that, as UI delegate methods, the web view delegate methods will always come in on the main thread, so that shouldn't be an issue. Nowhere else would threading come into play here.
Something that strikes me as a possible problem, though, is unrelated to your web view code per se.
Consider the interaction between
#property(nonatomic, retain) IBOutlet UIWebView* webView;
and
[self.webView release];
self.webView = nil;
Since you have declared webView as retained, won't self.webView = nil send a release to webView (and retain to nil, which of course doesn't do anything)? Isn't this then an over-release of webView?
(As a general rule, you shouldn't use accessors in init/dealloc, in part due to side-effects.)
EDIT:
To examine this further, I wrote a quick test using the view-based app template. Relevant portions below:
// In MyClass.m
+ (id)alloc
{
NSLog(#"alloc");
return [super alloc];
}
- (void)dealloc
{
NSLog(#"dealloc");
[super dealloc];
}
- (oneway void)release
{
NSLog(#"release");
[super release];
}
- (id)retain
{
NSLog(#"retain");
return [super retain];
}
// In UIViewController subclass
// .h:
#import "MyClass.h"
#property (retain, nonatomic) MyClass *myObj;
// .m:
#synthesize myObj = _myObj;
- (void)viewDidLoad
{
[super viewDidLoad];
[self setMyObj:[[[MyClass alloc] init] autorelease]];
[self setMyObj:nil];
}
This produced the following output, indicating that setting nil does, as I expected, release the old object:
2012-01-30 11:04:37.277 ReleaseTest[56305:f803] alloc
2012-01-30 11:04:37.278 ReleaseTest[56305:f803] retain
2012-01-30 11:04:37.279 ReleaseTest[56305:f803] release
2012-01-30 11:04:37.282 ReleaseTest[56305:f803] release
2012-01-30 11:04:37.283 ReleaseTest[56305:f803] dealloc
An, unsurprisingly, adding an extraneous release crashed the app with an EXC_BAD_ACCESS:
- (void)viewDidLoad
{
[super viewDidLoad];
[self setMyObj:[[[MyClass alloc] init] autorelease]];
[[self myObj] release];
[self setMyObj:nil];
}
2012-01-30 11:06:22.815 ReleaseTest[56330:f803] alloc
2012-01-30 11:06:22.817 ReleaseTest[56330:f803] retain
2012-01-30 11:06:22.818 ReleaseTest[56330:f803] release
2012-01-30 11:06:22.818 ReleaseTest[56330:f803] release
2012-01-30 11:06:22.819 ReleaseTest[56330:f803] dealloc
objc_release --> EXC_BAD_ACCESS in UIApplicationMain()
Of course, the code behaves identically if dot syntax is used (though dot syntax has the unfortunate effect of concealing the fact that it uses accessors, which is one reason I don't use it). I also tested by using an IBOutlet instead of instantiating in code. A lot more retain/releases, again unsurprisingly, but still a crash if I include an extra release:
2012-01-30 11:09:59.631 ReleaseTest[56389:f803] alloc
2012-01-30 11:09:59.633 ReleaseTest[56389:f803] retain
2012-01-30 11:09:59.634 ReleaseTest[56389:f803] retain
2012-01-30 11:09:59.635 ReleaseTest[56389:f803] retain
2012-01-30 11:09:59.636 ReleaseTest[56389:f803] retain
2012-01-30 11:09:59.636 ReleaseTest[56389:f803] retain
2012-01-30 11:09:59.637 ReleaseTest[56389:f803] release
2012-01-30 11:09:59.638 ReleaseTest[56389:f803] release
2012-01-30 11:09:59.638 ReleaseTest[56389:f803] release
2012-01-30 11:09:59.639 ReleaseTest[56389:f803] release
2012-01-30 11:09:59.640 ReleaseTest[56389:f803] release
2012-01-30 11:09:59.641 ReleaseTest[56389:f803] release
2012-01-30 11:09:59.641 ReleaseTest[56389:f803] dealloc
objc_msgSend --> EXC_BAD_ACCESS in UIApplicationMain()
So, in conclusion, I believe you do have a memory management error in your code, and this is responsible for some, if not all, of your crashes (which, I note, are of the same kind that I experimentally reproduced).
EDIT: This post is wrong and irrelevant to the OP. Struck-out the misleading text. Leaving rest here because I learned something in comments from Kevin below, and it may help someone else!
IF you don't need the webViewDidFinishLoad handling to occur on dealloc, set self.webView.delegate = nil; before calling stopLoading.
Also, you could manually call the webViewDidFinishLoad handling in the dealloc if any of it is needed and doesn't depend on the state of the webView.
I really wouldn't recommend putting a stopLoading in your dealloc. Just set the delegate to nil.
You should not design your dealloc to launch a lot of logic -- If your object is receiving a dealloc before its finished handling the logic you expect it to, then you are allowing it to be released it too quickly.

Analyzer warning about incorrect decrement of reference count

I just installed Xcode 4 and opened an earlier version of my app. The analyzer is reporting for this line:
[self.myViewControllerObject release];
incorrect decrement of the reference count of an object that is not owned at this point by the caller
I didn't enable ARC for my project.
When I analyze v2.0 of my app in Xcode 3.2.5, it doesn't show any potential error.
Header:
#class MyViewController;
MyViewController *myViewControllerObject;
#property ( nonatomic , retain ) MyViewController *myViewControllerObject;
Implementation:
#import "MyViewController.h"
#synthesize myViewControllerObject;
When a button is clicked I have:
TRY 1:
self.myViewControllerObject = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
[self.myViewControllerObject release];
TRY 2:
MyViewController *temp = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
self.myViewControllerObject = temp;
[temp release];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
[self.myViewControllerObject release];
TRY 3:
self.myViewControllerObject = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
In the dealloc method, I release it:
[self.myViewControllerObject release];
The warning comes from you calling release on a property through the accessor: when you do [self.myViewControllerObject release] you are actually calling the accessor method myViewControllerObject and then release on the return value. Since the name of the method does not begin with new, copy, or mutableCopy, you do not own the object it returns, hence you are not “allowed” to release it.
The solution is to never call release on the return value of that accessor, so basically your try #2 was fine:
MyViewController *temp = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
self.myViewControllerObject = temp;
[self.navigationController pushViewController:temp animated:YES];
[temp release];
But in dealloc do not use the accessor, rather:
[myViewControllerObject release];
If you need to release myViewController other than in dealloc, assign nil through the setter:
self.myViewControllerObject = nil;
Edit: For more on the subject, see Apple's Advanced Memory Management Guide.
The navigation controller manages the lifecycle of view controllers you add to it. In this way, when you push the view controller, you are passing 'ownership'. However, there's no reason you can't also maintain a reference to the view controller. Try this order:
self.myViewControllerObject = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.myViewControllerObject release];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
Alloc'd objects start with a retain count of one. Assigning a value to your 'retain' property increments the retain count by one. You should only keep one retain for the object, so you correctly release. But since you're starting at two, it's fine to release it before you pass it to the navigation controller. The compiler warning you see is just that - a warning, not always evidence you are doing something strictly wrong. It shouldn't matter if you pass it then release, but the order above should avoid it.
As I used the XCode 4 Beta version, the problem occurred. But when I tried it in the XCode 4 version, the Analyzer warning didn't occur to me. I Thank you to all whoever participated to help me.Thank you for your time.

Why release a property that you've already set to nil?

Here are two methods in a view controller from an Apple tutorial:
- (void)viewDidUnload {
self.eventsArray = nil;
self.locationManager = nil;
self.addButton = nil;
}
- (void)dealloc {
[managedObjectContext release];
[eventsArray release];
[locationManager release];
[addButton release];
[super dealloc];
}
Couldn't the dealloc method be shortened to the following? If not, why not?
- (void)dealloc {
[managedObjectContext release];
[super dealloc];
}
- (void)viewDidUnload is not guaranteed to be called, so you should always release things in dealloc too.
See this question to find out when it's called, and what you should do when it is.
No, because you cannot rely on viewDidUnload being called upon deallocation. viewDidUnload is only called when the view controller receives a memory warning while its view is not on screen. If the view controller gets deallocated, viewDidUnload is not called (AFAIK, I'm not entirely sure).
because it's a good practice to always cleanup your ivars in dealloc. something could go wrong, or you may encounter an execution you do not expect.
Setting eventsArray to nil just means it has no content, but still space for content
Calling [eventsArray release] releases the space the array consumed.