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

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.

Related

UIViewController not being released when popped

I have a table view that when a cell is selected it pushes a view controller onto the navigation stack:
SAPostTableViewController *postViewController = [[SAPostTableViewController alloc] initWithNibName:NSStringFromClass([SAPostTableViewController class]) bundle:nil];
postViewController.site = site;
[self.navigationController pushViewController:postViewController animated:YES];
[postViewController release];
SAPostTableViewController has a static tableView which, and it's cells, are loaded from a nib.
I have overridden the initWithNibName:bundle: method:
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
self.sections = [NSMutableDictionary dictionary];
}
return self;
}
sections is a retained property.
In viewDidLoad of SAPostTableViewController I have this:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(cellVisibiltyChanged:) name:#"SAStaticCellVisibiltyChanged" object:nil];
}
and so to match in viewDidUnload:
- (void)viewDidUnload
{
[super viewDidUnload];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"SAStaticCellVisibiltyChanged" object:nil];
}
However when I press the back button in the navigation bar (all standard behaviour, no override) and SAPostTableViewController is popped, it doesn't call viewDidUnload or dealloc. So this means that if I then reselect the cell that pushes SAPostTableViewController it creates a new instance of SAPostTableViewController and repeating this back and forward just means the memory usage keeps increasing as the popped SAPostTableViewControllers never get deallocated. (I know this by running Instruments on allocations)
The weird thing is that if I release SAPostTableViewController twice then it works as I'd expect:
SAPostTableViewController *postViewController = [[SAPostTableViewController alloc] initWithNibName:NSStringFromClass([SAPostTableViewController class]) bundle:nil];
postViewController.site = site;
[self.navigationController pushViewController:postViewController animated:YES];
[postViewController release];
[postViewController release];
(If I add a third release statement, it crashes as I'd expect it to with just 2)
I have resorted to using retainCount and stepped through the lines of code the are executed in the first line of the directly above code, the retainCount remains at 1. It jumps up between the first and second line, so I can't see anywhere it is being retain an extra time?
The SAPostTableViewController is only used in this place, it is not a delegate of anything, nor does it have a delegate.
How can I find a fix, or is it something simple I've missed?
Here is what Instruments shows after pushing SAPostTableViewController just once (with only one release statement):
And what it shows after navigating back and forth repeatedly (again, one release statement):
You are creating a new Object when you clic one Cell, why you don't create your Object (SAPostTableViewController) in the init Method and then Push the same object watch time you click in Cell
you can do something like this :
postViewController = [[SAPostTableViewController alloc] initWithNibName:NSStringFromClass([SAPostTableViewController class]) bundle:nil];
and in the
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
postViewController.site = site;
[self.navigationController pushViewController:postViewController animated:YES]; [postViewController release];
}
I don't know what your problem is, but here are some things to consider:
You should absolutely NOT release the view controller twice. If you have indeed discovered a memory leak in UIKit (which is unlikely), then it is likely it would be fixed in a future version of UIKit. That means that anyone running an old version of your app on a new version of the operating system would experience nothing but crashes (due to over-releasing the view controller). It is better to leak than to crash. A leaky app is still a usable app (at least until you leak too much). But a crashing app can't be run at all.
-viewDidUnload is not doing what you think it should be doing. It is only called when the view controller's view is unloaded due to memory pressure. It is not called during normal deallocation. It would be wiser to rely on -viewWillAppear: and -viewDidDisappear: instead.

call a method after the object is released?

i was reading this code, where setRegions is called after RootViewController is released : i find it a bit strange : does it mean RootViewController is still accessible, even if it was released and self.navigationController "owns" it ?
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Create the navigation and view controllers
RootViewController *rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.navigationController = aNavigationController;
[aNavigationController release];
[rootViewController release];
[rootViewController setRegions:[Region knownRegions]];
// Configure and display the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
Thanks
This is bad code.
An object should retain another object for as long as it cares about it. And in this case that rule is broken. The rootViewController is released, and then as you note, a method is called on it. This can be dangerous.
In this case, it works. This is because rootViewController is passed to another object, which retains it. So when we release it, it still has a positive retain count and is not deallocated. So our reference to it still works, and methods called on it work fine.
But lets say some implementation changed and initWithRootViewController: now no longer retained it's argument for some reason (an assumption you can't really make all the time). Suddenly this all crashes because rootViewController gets deallocated.
To fix this funk, you just need to move [rootViewController release]; to after the last useful reference of that object in this function. Your code then becomes more robust and more correct.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Create the navigation and view controllers
RootViewController *rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
[rootViewController setRegions:[Region knownRegions]];
UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.navigationController = aNavigationController;
// Release temporary objects since we've now sent them to other other objects
// which may or may not retain them (we don't really care which here)
[aNavigationController release];
[rootViewController release];
// Configure and display the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
Last thing to note: release and dealloc are very different things. release does not necessarily destroy objects. It simply decrements the retain count by one. And if that retain count ever gets to zero, only then is the object is deallocated. So this code works because a release happens but without triggering a dealloc.
The above is very dangerous code. It might happen to work, but it's just getting lucky. You should never access a variable after you have released it. In fact, it is best practice to immediately set variables to nil after releasing them if they don't immediately go out of scope. Some people only do this in Release mode, and so create a macro like:
#ifdef DEBUG
#define RELEASE(x) [x release];
#else
#define RELEASE(x) [x release]; x = nil;
#endif
The reason for this is to help catch bugs in debug mode (by having a crash rather than just a silent nil pointer), while being a bit safer in release mode.
But in any case, you should never access a variable after you've released it.
RootViewController *rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
(objectA created, retain count is 1, rootViewController points to it)
UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
(objectB created, retain count is 1, aNavigationController points to it)
(objectA retain count is 2 now, both rootViewController and some property in self.aNavigationController point to it)
self.navigationController = aNavigationController;
(objectB retain count is 2 now, both aNavigationController and self.navigationController point to it; assuming self.navigationController is a retain property)
[aNavigationController release];
(objectB retain count is 1 now, however, both aNavigationController and self.navigationController point to it)
[rootViewController release];
(objectA retain count is 1 now, however, both rootViewController and some property in self.aNavigationController point to it)
[rootViewController setRegions:[Region knownRegions]];
(use rootViewController to access objectA)
(This is not good)
Following is my recommended way:
RootViewController *rootViewController = [[[RootViewController alloc] initWithStyle:UITableViewStylePlain] autorelease];
[rootViewController setRegions:[Region knownRegions]];
UINavigationController *aNavigationController = [[[UINavigationController alloc] initWithRootViewController:rootViewController] autorelease];
self.navigationController = aNavigationController;

Dealloc Being Called Twice?

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];

Memory Management

How is method removeFromSuperView: really works?
I got a problem of memory bad access when I want to reinit the view
- (id)init {
if (!(self = [super init]))
return nil;
_mainView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
NSLog(#"retainCount :%d", [_mainView retainCount]);
UIButton *reInitButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f,0.0f,90.0f,35.0f)];
[reInitButton addTarget:self action:#selector(buttonDidTapped:) forControlEvents:UIControlEventTouchUpInside];
[[self view] addSubView:_mainView];
NSLog(#"retainCount :%d", [_mainView retainCount]);
[_mainView release];
NSLog(#"retainCount :%d", [_mainView retainCount]);
return self;
}
- (void)buttonDidTapped:(id)sender {
[_mainView removeFromSuperView]; //crash during second times press the button
NSLog(#"retainCount :%d", [_mainView retainCount]);
_mainView = [[UIView alloc] initWithFrame[[UIScreen mainScreen] bounds]];
[[self view] addSubView:_mainView];
NSLog(#"retainCount :%d", [_mainView retainCount]);
[_mainView release];
NSLog(#"retainCount :%d", [_mainView retainCount]);
}
I have NSLog every times there are any retain or alloc or release keyword. And the result is very weird.
//init
retainCount : 1
retainCount : 2
retainCount : 1
//1st time pressed button
retainCount : 1 //remove super view didn't decrease
retainCount : 2
retainCount : 1
//2nd time pressed button
retainCount : 0 //crash. Memory bad access
The weird thing is why it didn't crash on 1st time pressed??
I think your problem is here:
[_mainView release];
You've dropped your reference to _mainView, and yet, by my reading, that's a member variable that you'll keep around and continue to invoke methods on. That's not valid. Once you've called -release, you've essentially told the system you're not going to use that object again, and you can't do anything useful with a stale pointer to that object, like you do when you call -removeFromSuperView on it later.
If you want to continue to keep _mainView around and call code on it, you need to keep a reference. Perhaps you should move the release to your object's -dealloc method. Alternatively you could -release it in the button method and re-create a new view the next time you need to.
As a helpful tip, a lot of programmers like to reset objects to NULL (or nil in objC-speak) after releasing them, as a reminder that you can't use that object again. If you -release something, you'd better mean it.
Lastly, I suggest you Google the term "reference counting" and read up on it; it's a more generic idiom than the specifics of NSObject, and it is likely to be useful to think of the basics and how you might implement this in another language, like say, C. This will help you reason better about reference counted objects.
NEVER USE RETAINCOUNT. Sorry for putting that in caps, but I can't figure out for the life of me why people still use it. It's a faulty reference for memory management. Use instruments or similar instead.
You shouldn't be accessing _mainView at that point. This may be hard to explain, so bear with me. We're going to count, but not absolute retain count, just your code's claims on the object.
You allocate memory for an object and point at it with _mainView:
_mainView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
You have 1 claim of ownership to that object. When you add it as the subview of another view, that view likewise makes a claim of ownership, but that's not yours, it's the view's. The fact that it makes the object in _mainView stick around is an accident, and you shouldn't rely on it. Then you release the object:
[_mainView release];
You have relinquished your ownership claim -- you now have 0 claims, and you should no longer try to access this object. You don't own it. Again, the fact that it still exists because another view is using it, and the fact that you still have a pointer to it, are accidents*, and you should not rely on them.
When it comes time to handle your button press, then, you are accessing an object over which you have no ownership:
[_mainView removeFromSuperView];
and this causes a crash, which may not be expected, but it is not unreasonable. By letting your claims of ownership go to 0, you told the system "I don't need this object anymore. I'm not going to access it after this point. If it disappears, I will not be affected." In fact, though, you do need it to stay around, and you do need to access it.
What you should do, then, is move the line:
[_mainView release];
to inside the button action, right after the call to removeFromSuperview.
*The second of which could be avoided by setting _mainView = nil; after you release it, in this case, but that won't solve the greater problem.

UINavigationController crashes due to a NSArray variable

I push a view controller into current navigation controller. It works fine, except when I am getting out of the current view controller, it crashes.
MyTableView *newPage = [[MyTableView alloc] initWithNibName:#"table2" bundle:nil];
[[self navigationController] pushViewController:newPage animated:YES];
//[newPage release];
I comment out the last line to prevent crash. I read another post about variables being over released. In the newPage, I only have one variable (arrCellText), and is initialized in the initWithNibName
NSArray *temp = [[NSArray alloc] initWithObjects:#"string1", #"string2", #"string3", nil];
[self setArrCellText: temp];
[temp release];
I put the release in the dealloc
[arrCellText release];
If I comment out setting and release of arrCellText, it works fine too.
I must not have complete understanding of memory management, and I would like to understand this better. TIA
Where does the crash happen exactly?
First you can release 'newPage' after pushing it onto the navigationController (because it's retained there).
You might try to access anything from newPage after coming back. 'newPage' was released in the mean time and thus has some garbage value (but not nil).