UIViewController memory management - iphone

Hi I have a very basic issue of memory management with my UIViewController (or any other object that I create);
The problem is that in Instruments my Object allocation graph is always rising even though I am calling release on then assigning them nil.
I have 2 UIViewController sub-classes each initializing with a NIB;
I add the first ViewController to the main window like [window addSubView:first.view];
Then in my first ViewController nib file I have a Button which loads the second ViewController like :
-(IBAction)loadSecondView{
if(second!=nil){ //second is set as an iVar and #property (nonatomic, retain)ViewController2* second;
[second release];
second=nil;
}
second=[[ViewController2 alloc]initWithNibName:#"ViewController2" bundle:nil];
[self.view addSubView:second.view];
}
In my (second) ViewController2 i have a button with an action method
-(IBAction) removeSecond{
[self.view removeFromSuperView];
}
Please let me know if the above scheme works in a managed way for memory...?
In Instruments It does not show release of any allocation and keeps the bar status graph keeps on rising.

First of all, why use this scheme when second is a property:
if(second!=nil){
[second release];
second=nil;
}
second=[[ViewController2* second]initWithNibName:#"ViewController2" bundle:nil];
A propery automatically releases it's old value when the setter is used. So this could be rewritten as:
if(self.second == nil) { //Prevents creating a new controller if there already is one.
self.second = [[[ViewController2 alloc] initWithNibName:#"ViewController2" bundle:nil] autorelease];
}
Also, what's up with [ViewController2* second]?
Why is that asterisk there, and what does the class method second do?

Related

Memory leak in applicationWillEnterForeground

I am having around 9 ViewControllers in my application. I am opening the ViewControllers using the presentModalViewController. I want to display the 1st ViewController every time the user enters background and comes to foreground. If the user enters background in 8th ViewController and opens the application again , I need to display the 1st View Controller.
I am using the following code in the applicationWillEnterForeground
- (void)applicationWillEnterForeground:(UIApplication *)application
{
self.HomeScreenViewController = [[HomeScreenViewController alloc] initWithNibName:#"HomeScreenViewController" bundle:nil];
self.window.rootViewController = _homeScreenViewController;
[self.window makeKeyAndVisible];
}
and it is showing a memory leak. Obviously it will show the memory leak since I am initializing the HomeCtrl again, but I don't know how to fix it. Can anyone help me ??
I am not using the UINavigationController since the ViewControllers are pushed from right or Left side.
Change:
self.HomeScreenViewController = [[HomeScreenViewController alloc] ....];
to
_HomeScreenViewController = [[HomeScreenViewController alloc] ....];
Or you can do something like follow code:
HomeScreenViewController *tempHSVC = [[HomeScreenViewController alloc] ....];
self.HomeScreenViewController = tempHSVC;
[tempHSVC release];
self.window.rootViewController = self.HomeScreenViewController //or use just _homeScreenViewController;
[self.window makeKeyAndVisible];
And read some articles about memory management in objc.
I assume HomeScreenViewController property is declared as
#property (nonatomic, retain) HomeScreenViewController *HomeScreenViewController
Default setter method for retained properties will retain the object for you. Add autorelease to alloc init:
self.HomeScreenViewController = [[[HomeScreenViewController alloc] initWithNibName:#"HomeScreenViewController" bundle:nil] autorelease];
See also: Advanced Memory Management Programming Guide

unload View after calling presentModalViewController?

I have some view controller which I call with the following method:
myViewController *myView = [[myViewController alloc] initWithNibName:nil bundle:nil];
myView.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:myView animated:YES];
[myView release];
if I use the app a few times I get a memory warning and the app freezes for a few seconds! I think the reason is that i switch the view but not discharged the old one !!?!!?!!
(i set my outlets to nil and release them)
how can I unload the old view after switching to the new one?
Thanks in advance
When switching the view be sure to call dismissModalViewController:(BOOL)animated on myViewController.
In the class that launch the modalViewController you could make a property for the modal viewcontroller which you retain. Then you could write something like this.
//This would be in an action or something...
if (self.myViewControllerProperty == nil) {
self.myViewControllerProperty = [[[MyViewController alloc] initWithNibName:nil bundle:nil] autorelease];
}
[self presentModalViewController:self.myViewControllerProperty animated:YES];
Then instead of setting the
myView.modalTransitionStyle =
UIModalTransitionStyleCoverVertical;
Move that code to the modalViewController and write self.modalTransitionStyle = UIModalTransitionStyleCoverVertical; I think that looks cleaner, keep the configuration of each viewcontroller separted don't mix it up.
And as the maclema said, call dissmissModalViewController, but you probably are doing that...
Could be any number of problems but you don't need to (and can't) unload the old view. Make sure you are releasing objects and setting outlets to nil in viewDidUnload of all of your view controllers. viewDidUnload will be called when a memory warning occurs so if you don't handle it correctly you'll have leaks and can crash. Other than that, hard to know what else your app is doing that is contributing to the crash.

Allocating and releasing of UI controllers

I am new to iOS programming and I have read some useful articles on the release and allocation of memory and thought that i understand the concept. But during the actual coding, I can't seem to really apply the theory. I am really confuse over the fundamentals of iOS programming and hope that someone can help out. Thanks in advance!
1st behaviour - Some of the apps i see can maintain the current windows state when the iPhone home button is pressed so that when the app is launch next time, it will display the last state it is in.
2nd behaviour - Some other apps will behave like its a new startup everytime it is launch. Previously displayed text, images...etc will be cleared and it will always start at the first page.
What I wanted to do is like the 2nd behaviour - clear everything when the home button is pressed and start afresh everytime it is launch.
I have created a tab bar project. The codes below will result in the 1st behaviour.
I have tried releasing all the tab controllers at applicationDidEnterBackground instead at dealloc but it didn't work. It will still display the last screen.
My questions...
1) If i call release, shouldn't it destroy the window as well? eg. [NavController1 release]. It seems that the window can still work as usual...
2) What should I change to result in the 2nd behaviour?
3) If i assign tabBarController.viewControllers = nil, does it mean that the memory for the pages attached to it previously will be release?
4) Do i need to release IBOutlets variables? eg. UIWindow *window, UITabBarController, UITextField...etc. It didn't seem to give me memory leak even if i don't release them. The reason of this might be because I don't know how to kill the app. I tried to do the double click on the iphone home button to end the app but i still not able to get the debug to reach the dealloc function. An article says that if you alloc or retain it, you should release it. Since i'm not allocating anything here, can i not release it? Or is it possible to set autorelease?
#interface MyAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> {
UIWindow *window;
UITabBarController *tabBarController;
UINavigationController *NavController1;
UINavigationController *NavController2;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NavController1 = [[UINavigationController alloc] init];
NavController2 = [[UINavigationController alloc] init];
//This view controller inherits UIViewController and has a xib
FirstViewController *firstpage = [[FirstViewController alloc] init];
firstpage.title = #"First Page";
//These view controllers inherits UIViewController and delegate of UINavigationControllerDelegate and has a xib
SecondViewController *secondpage = [[SecondViewController alloc] init];
secondpage.title = #"Second Page";
[NavController1 pushViewController:secondpage animated:YES];
[secondpage release];
ThirdViewController *thirdpage = [[ThirdViewController alloc] init];
thirdpage.title = #"Third Page";
[NavController2 pushViewController:thirdpage animated:YES];
[thirdpage release];
// Add them as children of the tab bar controller
tabBarController.viewControllers = [NSArray arrayWithObjects:firstpage, NavController1, NavController2, nil];
// Don't forget memory management
[firstpage release];
// Add the tab bar controller's view to the window and display.
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, called instead of applicationWillTerminate: when the user quits.
*/
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
/*
Called as part of transition from the background to the inactive state: here you can undo many of the changes made on entering the background.
*/
}
- (void)dealloc {
[NavController1 release];
[NavController2 release];
[tabBarController release];
[window release];
[super dealloc];
}
1) As long as the retain count is 1 your view controller will be released by calling release.
In your code, dealloc will never be called, and therefore your view controllers will not be released anyway. You need to release everything in applicationDidEnterBackground...
2) in applicationDidEnterBackground do something like:
[NavController1 release];
NavController1 = nil;
[NavController2 release];
NavController2 = nil;
tabBarController.viewControllers = nil;
then recreate everything again in
(void)applicationWillEnterForeground:(UIApplication *)application
3) doing this will release firstpage, NavController1 and NavController2 (and any other view controllers you may have added)
4) yes you should. You do not get a memory leak in this instance because the delegate never gets dealloc'd and therefore there is still a valid reference to the objects. No you cannot autorelease as autorelease will release the object when the call stack returns to the run loop.

Removing UITabBarController

Someone has posted a similar question to this with no resolution, but also no sample code. So, I thought I would post my problem in detail here.
I have a game with several modes of play, each of these having several options. After playing around with various designs, it seems cleanest to put them into a UITabBarController with three tabs, one for each class of games. I created a new UIVIewController which is loaded from the main menu screen (replacing the main screen) and initializes the UITabBarController as follows:
barController = [[UITabBarController alloc] init];
Games1 *vc1 = [[[Games1 alloc] initWithNibName:#"Games1" bundle:nil] autorelease];
Games2 *vc2 = [[[Games2 alloc] initWithNibName:#"Games2" bundle:nil] autorelease];
Games3 *vc3 = [[[Games3 alloc] initWithNibName:#"Games3" bundle:nil] autorelease];
NSArray* controllers = [NSArray arrayWithObjects:vc3, vc1, vc2, nil];
barController.viewControllers = controllers;
[self.view addSubview:barController.view];
When the user selects a game, I remove the UIViewController from the window and deallocate as follows:
- (void)dealloc {
printf("Games released: barController: %d\n", [barController retainCount]);
[barController.view removeFromSuperview];
barController.viewControllers = 0;
[barController release];
barController = 0;
[super dealloc];
}
The problem I have is that when I rotate the device, I get a crash. If I launch a game mode directly from the main screen and rotate, no crash. I've verified that everything is getting deallocated, and my retain count on the bar controller is 1. Any suggestions on how to eliminate this crash? Thanks!
[Edit] A bit more info:
The barController is defined as:
IBOutlet UITabBarController *barController;
with:
#property (nonatomic, retain) IBOutlet UITabBarController *barController;
It ends up the problem was only peripherally related to the UITabBarController. I was adding and removing UIViewControllers directly to my app window, which has been shown to be causing problems elsewhere. Adding a master UIViewController / UIView and only adding and removing from that fixes everything, although an autorelease instead of a release may have worked as well. See the discussion here:
View Controller being sent a message even though it has been deallocated
The UITabBarController was just causing the problem to happen much more quickly and obviously.
Don't do this:
barController.viewControllers = 0;
In -dealloc you should only remove the UITabBarController's view from its superview and release it.

iPhone - Multiple UIViewControllers Release

My main UIViewController, (PMGameViewController.h), is the file which my apps delegate calls.
There are several buttons on my main UIViewController (PMGameViewController.m). When a button is pressed I do an insertSuvbiew and attach another UIViewController on top. When the mini game is over I simply do a removeFromSubview. This removes the UIViewController I inserted on top and shows me the main menu. Perfect this is what I want, but...
After I do a removeFromSubview, the objectalloc does not drop. How can I release that UIViewController's memory. I do not know of a way to backreference to my main UIViewController (PMGameViewController.m) to tell it that it has been removed and to release the UIViewController memory.
Here is how I insert the Subview
//////////////////////////////////////
//Buttons are in PMGameViewController.m file
//////////////////////////////////////
if((UIButton *) sender == gameClassicBtn) {
//////////////////////////////////////
//This Inserts the GameClassic.h file
//////////////////////////////////////
GameClassic *gameClassicController = [[GameClassic alloc]
initWithNibName:#"GameClassic" bundle:nil];
self.gameClassic = gameClassicController;
[gameClassicController release];
[self.view insertSubview:gameClassicController.view atIndex:1];
}
if((UIButton *) sender == gameArcadeBtn) {
//////////////////////////////////////
//This Inserts the GameArcade.h file
//////////////////////////////////////
GameArcade *gameArcadeController = [[GameArcade alloc]
initWithNibName:#"GameArcade" bundle:nil];
self.gameArcade = gameArcadeController;
[gameArcadeController release];
[self.view insertSubview:gameArcadeController.view atIndex:1];
}
I'm don't know why you want to do this, since you might need your PGGameViewController afterwards. But if you really want to release it, you could do this :
PMGameViewController *tmpViewController = [[[UIApplication sharedApplication] delegate] viewController(or however it's called)]
to backreference it, then do your stuff and release it when you don't need it :
[tmpViewController release]
If you have to keep the reference for a while, you could create an id ivar in your two games view controllers, and use an asign protocol, but don't forget to set it to nil after releasing the controller :
id tmpViewController;
...
#property (nonatomic, assign) id tmpViewController;
...
#synthesize tmpViewController;
You can set the view controller to nil after you remove it . Prior to setting it to nil you can optionally release it. Whether you release it or not depends on use and how expensive it is to load.