I've searched for this on Apple's site and can only seem to find documentation using Storyboards or Navigation Controllers, neither of which I'm using. It's an incredibly straightforward question about memory management.
I created a completely blank application. In my AppDelegate's didFinishLaunchingWithOptions function I'm creating an instance of a View Controller which I've built. My design (which itself could be a problem) is to have a 1:1 relationship between View Controllers and Views.
So the main menu of my application, which is a launching pad for everything is in MenuViewController.h/m.
In .h:
MenuViewController *m;
In .m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
m = (MenuViewController *)[[MenuViewController alloc] init];
m.window = self.window;
[m doStuff]; // handful of functions, not actually called this
//[m release]; // doesn't make sense to me
return YES;
}
This is where I'm confused. I want this to exist for basically the entirety of the application life cycle. But I'm also under the impression that you should (in the scope of the current function) release anything you allocate. If you need it beyond that, you should retain it elsewhere first. Is this not true?
My fundamental question is...where should I be releasing this View Controller? Is there anything else I've said that seems out of whack?
The initialization is wrong. You don't assign a window to controller, you assign a controller to window:
// window creation code here
...
m = [[MenuViewController alloc] init];
[window setRootViewController:m]; // window does retain for m
[m release]; // so we release it here
[self.window makeKeyAndVisible];
return YES
}
You're right. Generally you should release anything you create in a scope. But in this case you want ownership of the view controller. In this case you need to release the object in the dealloc method of your app delegate:
- (void)dealloc {
[m release];
[super dealloc];
}
Alternatively you could define a #property for your view controller with retain flag and then do this:
MenuViewController *viewController = [[MenuViewController alloc] init];
self.m = viewController;
[viewController release];
Btw, you don't need to cast to MenuViewController in either case.
EDIT: I completely missed that you don't add your view controller to your window. Good point #Eimantas.
Related
I have a UINavigationController as my rootViewController, which contains a UIViewController (which I will call projects for the sake of this discussion). In Projects, I have a button, which when clicked, I want to load a UISplitViewController - preferably sliding up from the bottom, although this is just a nice-to-have feature.
In the UISplitViewController, I have a "Close" button which I want to remove the UISplitViewController re-showing Projects.
From what I have read, UISPlitViewControllers must be the rootViewControllers. With that in mind, my code so far is as follows.
AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
...
// LOAD THE PROJECTS PANEL ROOT VIEW CONTROLLER INTO THE WINDOW
ProjectsListViewController *projects = [[ProjectsListViewController alloc] init];
navigationController = [[UINavigationController alloc] initWithRootViewController:projects];
[window setRootViewController:navigationController];
[window makeKeyAndVisible];
return YES;
}
ProjectsViewController
-(IBAction)loadDetails
{
ProjectNavigationController *projectNavPanel = [[ProjectNavigationController alloc] init];
ProjectDetailController *projectDetailPanel = [[ProjectDetailController alloc] init];
ProjectSplitViewController *splitRootController = [[ProjectSplitViewController alloc] init];
[splitRootController setViewControllers:[NSArray arrayWithObjects:projectNavPanel, projectDetailPanel, nil]];
[[self view] removeFromSuperview];
[[appDelegate window] setRootViewController:splitRootController];
}
UISplitViewController Naviagaion
- (void)loadProjects
{
// LOAD THE PROJECTS LIST BACK INTO VIEW
ProjectsListViewController *projectsList = [[ProjectsListViewController alloc] init];
[[[self parentViewController] view] removeFromSuperview];
[[appDelegate window] setRootViewController:projectsList];
}
Now, I know this is wrong, and unsurprisingly it is having adverse effects on other methods. In fact, as I type this, I noticed that the Projects page is being loaded in a navigationController on launch, but placed directly on the window when the splitViewController is closed. Can anyone help me by explaining the correct method of achieving this?
Thanks
Apple say that a UISplitViewController must be the topmost view controller in your app, and that it must be there for the entire lifetime of your app. As you've noticed, if you ignore this, everything can break.
There are some alternatives out there that don't break this way, e.g. MGSplitViewController. Google around. If you have the time, you could even cook your own implementation of a split view controller and be in complete control.
If you really want to use Apple's UIsplitViewController in "crazy" ways, then you can install it as root VC (as Apple demand), and have it at the root all the time, but then show other UIs modally over the top of it. Then hide the modal UI to make the split view controller appear. This is nasty and hacky though.
A while back I asked a related question that may be of interest:
Best way to switch between UISplitViewController and other view controllers?
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;
i'm trying to pass an NSArray from the appDelegate to the viewController but it seems that the data is not being retained. E.g. 'courseArray'contains values in the appDelegate but in the viewController its empty. What am i doing wrong?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
CourseSelectController *courseTimeTableView = [[CourseSelectController alloc] initWithNibName:nil bundle:nil];
courseTimeTableView.courseArray = self.courseArray;
[courseTimeTableView release];
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
self.viewController = [[[WestminsterViewController alloc] init] autorelease];
if([self.window respondsToSelector:#selector(setRootViewController:)])
{
[self.window performSelector:#selector(setRootViewController:) withObject:self.viewController];
}
else
{
[self.window addSubview:[self.viewController view]];
[self.viewController.view setFrame:[self.window bounds]];
}
[self.window makeKeyAndVisible];
return YES;
}
You assign courseArray to an instance of CourseSelectController and then immediately throw away that controller by releasing it.
Then, you create a WestminsterViewController and assign it as your window's root view controller, but that view controller was never assigned courseArray.
Well you are releasing the courseTimeTableView immediately after you assigned the courseArray.
There are some serious issues with your code.
In the second line, when you set courseTimeTableView.courseArray with self.courseArray, self.courseArray just returns nil (assuming you are using regular synthesized properties).
Then in the third line, you release the view controller! It only had a retain count of 1, so it is deallocated and no longer usable.
Start by fixing these :)
I used the instruments tool and it tells me I have no memory leaks. But I am struggling with the logic behind it. Why wouldn't this be leaky? It appears that some magic is happening behind the scenes and my AppDelegate is using my allocated navController to set the property of the self.navigationController. Without my initialization here, my property is nil.
Shouldn't I be forced to make these instance variables of the delegate and then release them in the dealloc? Why isn't this a leak? Or am I using the instruments tool wrong?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// two alloc calls which would imply I need a release
UINavigationController *navController = [[UINavigationController alloc] init];
UIViewController *calcController = [[CalculatorViewController alloc] init];
[navController pushViewController:calcController animated:YES];
[window addSubview:navController.view];
[window makeKeyAndVisible];
// can not release here, if i do, no views show up
// [navController release];
// [calcController release];
return YES;
}
...
// NOTE: No dealloc for navController or calcController
- (void)dealloc {
[window release];
[super dealloc];
}
Both navController and calcController exist for the life of the program. When the program terminates, everything is purged, so it doesn't matter. You do have a leak, but an irrelevant one.
Since your app delegate exists for the duration of the application run, you would only see a memory leak right before the application quits. So while there is technically a "leak" it only occurs right before the application is purged from memory.
I was wondering something about the app delegate of my app.
Why can't I release like this :
-(BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
RootViewController *controller = [[RootViewController alloc]
initWithNibName:#"RootViewController"
bundle:[NSBundle mainBundle]];
[self.window addSubview:controller.view];
[controller release]; // Here's my question
[self.window makeKeyAndVisible];
return YES;
}
I was almost sure that -addSubview method increase by 1 my retain count. So why do I have crash when I release my controller ? Why is it working in another class but the delegate ?
Thanks !
The other answers are correct, the UIVIewController is not being retained, what I recommend is setting the UIWindows rootViewController (only available iOS 4.0 and later) property which does retain the controller. If your app supports pre iOS 4.0 then you will need to store controller in an instance variable.
-(BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
RootViewController *controller = [[RootViewController alloc]
initWithNibName:#"RootViewController"
bundle:[NSBundle mainBundle]];
//controller will be retained and view will set for you
window.rootViewController = controller;
[controller release];
[self.window makeKeyAndVisible];
return YES;
}
This line
[self.window addSubview:controller.view];
increases the retain count of controller.view not controller. That's why
[controller release];
creates a problem.
If this is the main window, then you don't need to worry about the memory leak, because the window is active for the entire life of the program, and all memory is purged when it terminates.
addSubView increases the retain count of the view inside of the view controller, that is why the app crashes if you release the controller.
in any case, if you don't release it, you will have a leak. the solution is creating an ivar in your class and assigning to it the view controller (instead of a local variable), then release it in dealloc.
When you add view as subview then view gets retained, not its controller. So when you release controller it gets deallocated and its view - not. As result later view try to send messages to its already deallocated controller and app crashes.
This because you are the unique owner of that controller. You just add its view as a subview of the window. Although the view gets retained by the window's view, the controller not.
So, it will get deallocated and any further use of it will make your app crash.