I recently changed my app to use a UINavigationController, I was using a UINavigationBar before, with cascade subView adding, which was a bit tenuous.
I'm facing a problem of memory usage. Leaks tool doesn't show any leak, but ViewControllers I create and add to the UINavigationController never seem to be released. So memory usage grows everytime I create a new VC and then press the NavigationController's back button.
I simply create and add my VCs this way:
DetailViewController* detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
// setups
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
The app never goes through ViewController's dealloc and viewDidUnload methods. Shouldn't these be called everytime I press the back button?
I've searched many tutorials and read Apple's memory management, but there's nothing about VC's lifetime in memory when using NavigationController.
Maybe you are not doing something wrong and instead you are facing something like this
In the Blog post it was the question whether we have to manually release IBOutlets or not. As it turns out we should. This was reproduceable in iOS 3.1.3 but I didn't test it in iOS 4.0 yet.
The second aproach is to override your view controllers retain and release method and print out the retain count. I had a simimlar problem, that some view controllers dealloc method did not called so I override this methods to see wether someone has still a retain on it. As it turns out it did.
Edit:
When I printed my retain count, it would sometimes reach ~98 caused from the framework, so thats not really to worry.
If your last retain count stays at 2 and the dealloc method won't be called, than there is someone that has still a retain on it.
In this case you should search on other places.
For example another problem I encountered during this same problem:
Sometimes I would use
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateUI) userInfo:nil repeats:YES]
to constantly update the UI. But what I forgot was, that the NSTimer will retain the target object (which was the ViewController). Because the NSTimer retained your view controller your dealloc will never be called because someone (NSTimer) has still a retain on it. So you have to make sure to invalidate the NSTimer BEFORE dealloc method to properly release the view controller.
Edit2 in response for a comment below:
A retain declaired property does as follows (exsample):
- (void)setTarget:(id)value {
if (value != target) {
[target release];
target = [value retain];
}
So it does first release your current self.target then retains the new value. Since you are assigning nil your target will be nil afterwards. Further info about Properties can be found in the Apple doc.
I have seen this as well. As you pointed out, I haven't seen anything definitive in the docs, but my belief is that they are retained in memory until memory is needed. It makes sense from a performance perspective as doing so allows the app to quickly navigate between the different views.
Bottom line is, I wouldn't worry about it. You could fire off some Low Memory Warnings in the Simulator and see if it actually releases your VCs.
Related
I have been developing iphone applications for around 3months now and theres a few things that stump me and i don't really have an idea how to work round them.
I have a navigation controller controlling the views in my application however every screen that is loaded, used then pushed back loses all the information as it seems to be reinstantiated... I believe this is a possible memory management issue?
But how to i create an app that navigates and retains all information in its views until the application is closed.
Thanks :)
Possible you didn't keep a reference to the view controller, the issue is for UIVIewController not to be released.
Make the view controller an ivar you will instanciate only one time when you push it on stack.
// in .h
MyViewController *mVC;
// in .m
// maybe when the user selects a row in a tableview
if(mVC == nil) {
// first time use, alloc/init
mVC = [[MyViewController ....];
}
// then push on the stack
[self.navigationController ....];
Of course don't forget to release it later.
In this part:
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];
You will probably have something like this... Instead, make your myViewController a class's property so you have a reference to it. And drop the [myViewController release]; statement.
Possibly your app is receiving a didReceiveMemoryWarning.
In such cases, when the super class is called, the framework does memory cleaning by unloading all the views that are not currently displayed. This could explain the behavior you are seeing.
To check it further, override didReceiveMemoryWarning in one of your view controllers or applicationDidReceiveMemoryWarning in your app delegate, and put a breakpoint in it. Don't forget to call [super...] appropriately, otherwise pretty soon your app will be killed. What you should see in this way is that the views do not disappear before hitting the breakpoint, and do disappear after that.
If the hypothesis is correct, you should find a way to save the state of your view in viewDidUnload and restore it in viewDidLoad. Look also at didReceiveMemoryWarning reference.
Try to save data in NSUserDefaults it its small or use plist or it its too small like 5-10 objects save in in some variable in appDelegate, and if its too large use sqlite and for saving something like images of files like xml use Document directory
The UINavigationController works like a stack: you push and pop UIViewControllers on it. That means when a UIViewController get popped, it will have its retain count decremented by 1, and if no other object holds a reference to it, it will be deallocated. You can avoid the UIViewControllers getting dealloced by keeping a reference to them yourself by calling -retain on the objects, for instance in your appDelegate.
You can use NSUserDefaults to save the states of the UIControls in the view.
So whenever u r loading a view, set the values to the controls so that it looks like it resume from the place where we left.
I'm diving into iOS development and I'm building a navigation based app that wasn't fully releasing one of the views that was being pushed onto the nav stack. This is problematic because the view controller is never being deallocated, so the memory it uses just builds up every time the view controller is pushed on to the stack. So after investigating the issue, I found the retain counts for the view controller were really strange. The view controller in question is pushed on to the stack once a countdown timer reaches zero.
Here's the code that creates the view controller in the timer callback, displays its retain count, and pushes it onto the nav stack...
-(void)updateCountDownTimer //Defined in MyViewController_A class
{
[self setTimeRemaining:([self timeRemaining] - 1)];
[[self countDownLabel] setAlpha:1];
[[self countDownLabel] setText:[NSString stringWithFormat:#"%d", [self timeRemaining]]];
//Fade out the current time
[UIView beginAnimations:#"FadeAnimation" context:nil];
[UIView setAnimationDuration:1];
[[self countDownLabel] setAlpha:0];
[UIView commitAnimations];
if ([self timeRemaining] == 0)
{
MyViewController_B *myvc_b = [[MyViewController_B alloc] initWithNibName:#"MyView_B_iPhone" bundle:nil];
[[self navigationController] pushViewController:myvc_b animated:YES];
NSLog(#"updateCountDownTimer: %d", [myvc_b retainCount]);
[myvc_b release];
[[self countDownTimer] invalidate];
[[self countDownLabel] setHidden:YES];
}
}
and here's the code that pops the view controller off the nav stack once the pause button is pressed...
- (void)pauseButtonPressed:(id)sender
{
//Stop the timer
[puzzleTimer invalidate];
NSLog(#"pauseButtonPressed before pop: %d", [self retainCount]);
//return to the previous view
[[self navigationController] popViewControllerAnimated:YES];
NSLog(#"pauseButtonPressed after pop: %d", [self retainCount]);
}
and here's the console output that shows really strange retain counts throughout the process...
2010-12-02 17:50:38.062 MyApp[821:307] updateCountDownTimer: 5
2010-12-02 17:50:40.453 MyApp[821:307] pauseButtonPressed before pop: 2
2010-12-02 17:50:40.462 MyApp[821:307] pauseButtonPressed after pop: 4
I'm new to iOS development, but the code seems pretty straightforward to me, so I don't know what I'm missing.
Thanks so much in advance for your wisdom!
UPDATE: It looks like the Leaks instrument is reporting a leak on the line of code that pushes the previous view controller onto the stack (that is, the view controller responsible for pushing the view controller in question). The code is once again very straightforward, so I don't know why it's reporting a leak...
MyViewController_A *myvc_a = [[MyViewController_A alloc] initWithNibName:#"MyView_A_iPhone" bundle:nil];
[[self navigationController] pushViewController:myvc_a animated:YES]; //<--Leak being reported here
[myvc_a release];
*UPDATE:*Found the problem, it was just as everyone was saying and the same problem as was shown in the link posted in the comments below, I had live objects still referencing my view controller, which prevented it from deallocating. In my case, I had two timers that were targeting my view controller and those timers weren't being invalidated before I popped the view off the stack, which meant there were two live objects still referencing the view controller. Here's a snippet I found in the Apple docs which uncovered the problem...
Perhaps more importantly, a timer also
maintains a strong reference to its
target. This means that as long as a
timer remains valid (and you otherwise
properly abide by memory management
rules), its target will not be
deallocated.
Anyhow, thanks again to everyone who helped!
You're not missing anything -- instances of UINavigationController just do strange, strange things to the retain count internally.
You should only worry about the retainCount if you see a specific memory leak that you're trying to patch up. In this case, of course, you DO have a problem... the retainCount just doesn't help, since it's so bizarre.
You might check if dealloc is getting called on MyViewController when you do the pop. Also, before you test, comment out the lines that check the retainCount. Calling retainCount will sometimes add to the retainCount.
To really nail down what's going on, in Xcode, go to the Run menu, and select Run With Performance Tool > Leaks. Push and pop that view controller, and you should see it pop up as a leak. You'll be able to see all of the retain and release calls on the object.
If you're really stuck, Apple's guide to Finding Leaks has a few more clever solutions. Good luck!
There is nothing wrong with your code as far as memory management is concerned.
You should not rely on retain counts to check if your objects are being released properly as the system will also be retaining what it needs and releasing when appropriate. For instance when you add your view controller to the stack it gets retained by the nav controller along with its subviews and when it's popped out it gets send a release message which propagates all of its subviews.
The general rule is if you alloc, retain or copy an object, it is your responsibility to release it. Everything else is dealt with by the system and will be flushed with the auto release pool.
Never, ever, ever look at the retain counts of your objects. They should not be used programmatically, and are misleading when you are trying to debug your code.
Here's why: You know that in your code, you are making calls to retain and release to manage your retain count. But you may also be making calls to -autorelease, which causes your retain count to be decremented at a later date over which you have little or no control. Worse, any time you pass a reference to your object to an object that you do not control the implementation of (which is likely to happen to most of the objects you create), the receiving object may make its own adjustments to the retain count - and that object may pass your object to yet other objects, which also adjust the retain count.
The point is, you should not ever look at the retain count of your objects for any reason at any time. They will only serve to confuse you. Your job is to manage your own claims to an object correctly, and trust that the code written by others is doing so as well.
I kind of understand why I'm getting this analyzer warning. Because I'm using an object that is being passed in. I've tried autorelease and retain however these cause me other problems like unrecognized selector sent to instance.
The aim of my CommonUI function is to re-use code, but I have to cater for addSubView and presentModalViewController.
Perhaps I'm doing some obvious wrong ?
Change your code like this:
HelpViewController *helpvc = [[HelpViewController alloc] init....];
[vw addSubview:helpvc.view];
[helpcv release];
I think you don't need to pass the other VC.
There are two problems here.
First, if you call [vc release] (as the other answers suggest), you'll certainly make the analyzer happy but likely crash the app. A view controller's view doesn't retain the controller, so any button targets in the view will be pointing to garbage.
You will need to somehow keep the HelpViewController retained for as long as it is showing up onscreen. The "parent" view controller should likely retain it somehow. You could autorelease it, and return it. Then whomever calls showHelpClick... would retain the returned controller.
Second, you don't need to have the (UIViewController *)vc passed in as an argument.
i am using UIviewcontroller subclasses. In my main view i have 3 buttons, each button will load a different nib. and each new nib is having one back button to come back to main view.
when i click one the back button of any view to move to the main view the dealloc of that view is not getting called? i didnt understood this.
can anyone explain when those views dealloc will be called?
if the dealloc method hasn't been called, it means that your retained your viewController object by hands. for example, in this case dealloc will not be called after clicking back button to return
MyViewController *controller = [[MyViewController alloc] init];
[self.navigationController pushViewController:controller animated:YES];
You should add
[controller release];
to this code to be sure that your instance of viewController will be deallocated. If you are absolutely sure, that you had sent equal number of alloc(or any message that increases object's retainCount) and release messages for your object and dealloc method doesn't be called anyway, it will be more complex. I hope that this answer will help. If you will find that your situation is "more complex", post a comment, then I'll try to explain with more details.
I too would like to dive deeper into understanding memory management details (below surface level) where it comes to controllers being pushed on and off of the stack. I built my framework from the text, "Beginning iPhone 3 Development" by Mark and LaMarche, but that text effectively re-uses sub-controllers and their dealloc methods never get called.
I have noticed that repeated use of a sub-controller with a NIB containing a UIWebView that calls Google's web directions url ... eventually results in a memory warning and my data is lost. This involves repeated "reuse" of the sub-controller.
If you can point me as well to in depth text that goes into nav controller and sub view memory management, that would be excellent.
Whenever I add a new viewController my ObjectAlloc jumps up really high and never comes back down. Even after calling removeFromSuperview. Is this normal?
if((UIButton *) sender == gameArcadeBtn) {
GameArcade *gameArcadeController = [[GameArcade alloc]
initWithNibName:#"GameArcade" bundle:nil];
self.gameArcade = gameArcadeController;
[gameArcadeController release];
[self.view insertSubview:gameArcadeController.view atIndex:1];
}
Instantiating a view always creates many objects.As long as this view is in memory or has not been autoreleased, the objects will remained alloced in memory. Thus, to answer your question, this is normal.
It sounds like you are worried about memory usage and while it is important to watch the object allocs so that it doesn't get too it is more important to find your app leaks.
Some memory management tips:
1) do lazy loading. Only load your views when the user asks for them, not all at the beginning of the app
2) remove everything that you possibly can when you dont need it anymore. This means doing tons of work in viewWillAppear and viewDidDisappear
3) learn about #properties and how it relates to autoreleasing, and do not use properties for everything.
4) As appealing as it is, avoid autorelease and manually release objects when you dont need them anymore.
that's probably due to the fact that you're still retaining the view's controller in the class. try releasing that