I have some memory issue with my iPhone application and I have no clue what is happening.
So, I observed that the memory usage of the application is rising continuously when going from an UIViewController to another. I have used the "Mark Heap" tool from the "Allocations" instrument and it seams that the only objects that are not deallocated are my UIViewControllers.
To be more specific I have let take my two UIViewControllers. The first one is named PuzzleViewController and the second one is named Options. When the app starts, the PuzzleViewController appears. I mark a heap here, to set a baseline, and after this I press the "Options" button which will present the Options UIViewController. I go back to the first one and I mark a heap again. After repeating these steps over and over again (like 20 times or so :D) I observe that after every Heapshot I have about 22 objects remaining alive. Two of those objects are instances of my UIViewControllers.
I really don't have any clue what is happening.
Here is how I switch to the Options UIViewController:
- (IBAction) onOptionsButton: (id) sender
{
Options *viewController = [[Options alloc] init];
[self presentModalViewController:viewController animated:YES];
[viewController release];
}
And here is how I go back to the PuzzleViewController:
- (IBAction) onMainMenu:(id) sender
{
PuzzleViewController *modalView = [[PuzzleViewController alloc] init];
[self presentModalViewController:modalView animated:YES];
[modalView release];
}
My viewDidUnload functions are called properly, but no dealloc function is ever called.
Thank you,
Andrei
You should call dismissModalViewController, not presentModalViewController again, nor recreate your PuzzleViewController.
Dismissing a Modal View Controller
Related
I'm still struggling a bit with the idea of ARC. Let's suppose I have two very complex viewControllers A and B that each have a lot of pictures in them which are retained by each view. For argument's sake, let's suppose the first ViewController (A) retains images which take up 75 MB in RAM. The other one (B) takes up 75 MB as well.
In my App Delegate I set up my NavigationController like so:
ViewControllerA *vcA = [[ViewControllerA alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:vcA];
[navController.navigationBar setHidden:YES];
[[self window] setRootViewController:navController];
When I switch from A to B, I do it like so in ViewControllerA.m:
ViewControllerB *vcB = [[ViewControllerB alloc] init];
[[self navigationController] pushViewController:vcB animated:YES];
When I switch back, I do it like so in ViewControllerB.m:
[[self navigationController] popToRootViewControllerAnimated:YES];
Now my big question is if I still have ViewController A in my memory when I'm in ViewController B? In this case, when does the compiler release a ViewController? Could I or should I release (i.e. set it to nil) one ViewController when it is not in use?
I'm sorry if the answer is clear or if I'm totally missing the point. So any answers and explanations would be highly appreciated.
Yes, you still have ViewControllerA (you can see this with Instruments next time). That has nothing to do with ARC, it's the same without it. Let me explain:
You create a UINavigationController and put UIViewController A as the root, A is retained (in ARC it's a strong property or something like that), as you can see UINavigationController needs it right?
Now you push UIViewController B, B and A exist on memory, you UINavigationController still needs UIViewController A, it's just not showing and the view can be unloaded, if the system needs memory, but it won't release A. When you pop UIViewController B, it is released, and if there aren't references for it (again, I assume this is how ARC works) it is dealloced.
Now your question is, when is the rootViewController dealloced? Well, UINavigationController always has a root! So, while you have a UINavigationController you have a rootViewController.
Let me know in the comments if you need further explaining.
I can't help you with ARC, cause I never used it (and I don't know if I really want).
But I can tell you one thing :
When you push your ViewControllers, they all are in the navigation stack. And untill they are in the stack, they remain in memory.
Without using ARC, if I autorelease eatch viewController I push, it will be released exactly when I would pop it from the stack.
If someone know more about ARC (and when it release allocated object) I would be glad to have more info.
Thanks
Your view controller A will be retained by navController so it won't be released. Even you set vcA to nil it will not be released because navController is retaining it.
The problem is that your controller retained lots resources (images) that takes lots memory. To solve this, you can allocate the resources on viewDidLoad and free them at viewDidUnload
for example
// in your view controller
- (void)viewDidLoad {
[super viewDidLoad];
self.image = // read image to memory
}
- (void)viewDidUnload {
[super viewDidUnload];
self.image = nil; // release the image to free memory
}
Then after view controller B is pushed to navController, view controller A will get notified by UIKit that it is not the displaying controller and it will unload the view if necessary in order to free memory for other class to use.
I think this is a pretty common usecase as I have seen it in several apps. But after spending couple of days, I am still struggling with this. I have a structure like the following:
UITabBarController
-- UINavigationController1
---- UITableViewController1
-- UINavigationController2
---- UITableViewController2
Now I have a logout button on UITableViewController2. When I click on that logout button I want all and any viewcontroller to be deallocated, all view unloaded. Basically start fresh like launching the app. I basically want the viewDidLoad on each of those UITableViewController called again.
I tried the following method to be called in my appdelegate when the logout action on UITableViewController2 is taken.
-(void) logout {
for (UINavigationController* ctrl in self.tabBarController.viewControllers) {
[ctrl popToRootViewControllerAnimated:NO];
ctrl.visibleViewController.view = nil;
}
[self.tabBarController.view removeFromSuperview];
[self.window addSubview:self.tabBarController.view];
}
But alas, it does not seem to work?
Any ideas how such a thing is accomplished? Also I see different behaviors in iOS4 vs iOS5 with the visibleViewController. I am not using any modal viewcontroller here. Any gotchas?
Update: I am not using ARC
thanks
mbh
Your for-loop will release and thus dealloc any view controllers that you have pushed onto the respective UINavigationController roots (depending on how many tabs you have), i.e. as these will not have a superview when you pop back to the root of each navigation controller, these are dealloc-ed automatically. These are your UITableViewControllers taken care of.
As for the respective UINavigationControllers, you would need your tabbar-controller to release the old instance. IMHO, this should be done for you when you release the UITabBarController.
This then leaves the UITabBarController itself. I don't think it can be done tbh. Your code will only remove the view, but not dealloc the tabbar controller itself. And as Krishna K points out, you need at least one view controller to reload all others.
Putting the code into the appdelegate makes sense, but you need to ensure that your logout() will not cause a retain on the UITableViewController2 as well as the UITabbarController as it's called from UITableViewController2 somewhere.
One idea to explore, does your AppDelegate hold an instance to the TabBar-Controller which you could release and create a new instance after removing the view from self.window?
// manually create UITabBarController - AppDelegate holds instance
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
mytabcontroller=[[UITabBarController alloc] initWithNibName:#"foo" bundle:nil];
}
- (void) logout {
[self.tabBarController.view removeFromSuperview];
[mytabcontroller release];
mytabcontroller=[[UITabBarController alloc] initWithNibName:#"foo" bundle:nil];
[self.window addSubview:self.tabBarController.view];
}
But as I said, there might be caveats with memory management at this point.
You need to release your view controllers. When their release method is called, that method should include statements to release all of its object's resources (and also dealloc its superclass).
Your rootViewController for both Navigation controllers are their respective TableView controllers. So I don't think popToRootViewController would do anything.
You probably need to reset the data and refresh the views instead of deallocating the views.
Ok - my brain is being fried at the moment so any help would be appreciated.
I have multiple subclasses of UIViewController in my app. lets call them VC_A, VC_B, VC_C, VC_D.
The users interacts by touching buttons on each of the views.
So my AppDelegate adds in VC_A:
//Add the view controller's view to the window and display.
[self.window addSubview:viewController.view];
[self.window makeKeyAndVisible];
VC_A then loads VC_B by using presentModalViewController:
VC_B *tempView = [[VC_B alloc] initWithNibName:#"temploadingscreen" bundle:nil];
[self presentModalViewController:tempView animated:NO];
[tempView release];
and so until I get a hierarchy of
VC_A
- VC_B
- VC_C
- VC_D
but then when I call presentModalViewController on VC_D to take me to VC_C I want it to be a new instance of VC_C and not the original instance.
So my question is how to you go about doing this - do I need to use [self dismissModalViewControllerAnimated:NO]; to remove the old instances of the views.
Any help would be gratefully appreciated as I have done searches for this but all the tutorials and stuff use a navbar to control the navigation - and i cant use one because of the type of app. Any working code examples of properly moving between new instances of UIViewControllers would be great.
Just create a new instance with
ViewController_C *newVC_C = [[ViewController_C alloc] init]
[self presentModalViewController:newVC_C animated:NO];
[newVC_C release];
I decided to do this a different way which works perfectly for what I need.
What I did was I created the base ViewController with nothing in the xib and in the viewDidAppear method I called the other viewControllers (using presentModalViewController) based on the value of a global NSNumber.
Thus when I go to any of the other viewcontrollers rather than them call another viewController they simply set the global variable indicating which view to load and then close the current view (using dismissModalViewController).
This way each instance of the viewControllers are closed and the memory released.
I have created an example project and placed it on github https://github.com/sregorcinimod/Open
Just look in the Downloads you'll see it there
So I have a UINavController in my app and am trying to execute a method when the user presses the back button. I have searched everywhere and can only find bits and pieces that don't really make sense out of context.
Is there a way to implement some sort of check that catches when the user presses the back button to dismiss the current view? (the viewWillDisappear method for the view being popped never gets called for some reason. I did read that it doesn't unless you forward that call?) Does that sound right, and does anyone have any ideas or suggestions? Thanks in advance.
Take a look at the UINavigationControllerDelegate. There are the only two methods that get called when a UIViewController is pushed to the navigation controller stack. Similarly, if one is being pushed then something probably was just popped. This is what I did to call viewDidDisappear and viewWillDisappear.
# pragma mark - UINavigationControllerDelegate Methods
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
static UIViewController *vcPointer = nil;
// If the previous view controller is still around then let's send a viewWillDisappear message
if (vcPointer != nil) {
if ([vcPointer respondsToSelector:#selector(viewWillDisappear:)]) {
[vcPointer viewWillDisappear:animated];
}
}
// Keep track of a pointer to the current viewController
vcPointer = viewController;
[viewController viewWillAppear:animated];
}
This code keeps a pointer reference to the last view controller that was pushed so that once we push another one we can pop the last one (if it still exists).
AFAIK, if you add a UINavigationController to a UIView via code, it won't send those messages to it's subviews by default. It will only do this if the UINavigationController received these calls itself. Maybe this is your problem (I don't know your view setup).
So, when adding the view of the UINavigationController, be sure to manually send it these messages.
UINavigationController *navigationController = [UINavigationController alloc] initWithRootViewController:rootViewController];
[navigationController viewWillAppear:NO];
[aView addSubview:navigationController.view];
[navigationController viewDidAppear:NO];
At least, this is what I found during development. Been searching for this for a long time and I still don't understand the rationale behind it.
You can always hide the default back navigation button and create your own with its own method to be called when pressed.
Execute whatever code you want there then pop the view.
I used this solution:
Add a custom button on the left side in the navigation bar
Let that button activate a custom method.
Disadvantage of this workaround: you will lose that nice arrow shaped "back" button. That can be solved as well with a custom image.
So here is my code.
Put this in your viewDidLoad:
// LeftButton in Navigation Bar
UIBarButtonItem *leftBarButton = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:self action:#selector(backButtonPushed:)];
self.navigationItem.leftBarButtonItem = leftBarButton;
[leftBarButton release];
Then add this method in the same .m file:
- (void) backButtonPushed: (id)sender {
// do what you want to do
}
dont forget in the .h file
- (void) backButtonPushed: (id)sender;
The viewWillDisappear & viewDidDisappear is called when a controller is popped or dismissed. The function is called on the fore-front view controller not on the UINavigationController itself. Did you possibly subclass and forget to call the super on something?
I was watching CS193P Stanford course on Itunes, and in one of the lectures a demo was given and
There it was said you could present the viewcontroller modally and then release it. Roughly like this (I know this isn't perfect but I'm on my PC atm)
[self.view presentcontentmodally:myVC]
[myVC release];
However this seems to produce problems. If I put a NSLog(#"%d", [myVC retainCount]) between those two lines then it returns 2 implying it is ok to release. However when I dismiss the myVC the app crashes. Nothing in the NSlog and the debugger won't show where it stopped.
But I used malloc-history or something that some blog said would help. And found that it was the myVC.
So should I be releasing myVC?
(also when the modalVC has been dissmissed should the app's memory usuage go back to before the modalVC was presented?)
Yes, you should release your view controller after passing it to a modal navigation controller. Just make sure you are not passing in a previously retained view controller unless you plan to manage its release manually.
For example, follow the lifespan of _myViewController here:
MyViewController *_myViewController = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
UINavigationController *_modalNavigationController = [[UINavigationController alloc] initWithRootViewController:_myViewController];
[_myViewController release], _myViewController = nil;
[[self navigationController] presentModalViewController:_modalNavigationController animated:YES];
[_modalNavigationController release], _modalNavigationController = nil;
The modal navigation controller will increment the retain count of _myViewController, essentially taking ownership of it.
Once the modal navigation controller is dismissed and you are back to your original navigation controller, the modal navigation controller will receive a release message and in turn release its root view controller (_myViewController).
The retain count of this view controller will hit zero and the memory is then reclaimed.
I have just checked through a couple of my apps, and I am releasing my modal view controllers after each presentation, without problems. Which makes me think that you don't yet understand the Cocoa memory management model. Here's a sample:
TweetController *tweetController = [[TweetController alloc] init];
tweetController.content = content;
tweetController.delegate = self;
[self presentModalViewController:tweetController animated:YES];
[tweetController release];
Note that this controller was created with alloc/init, and wasn't previously released or autoreleased.
In addition, please don't rely on retain count checking; a retain could be from a previous autoreleased, which will go away very soon causing the sort of error you have been seeing.