I have a question, can this line cause any trouble?
self.window.rootViewController = self.viewController;
assuming both properties of self have a retain setter:
#property(nonatomic, retain) UIViewcontroller viewController;
my question is does this cause any weird object trees or retain cycles or memory problems? viewController has a retain count of 2 so if self wants to get rid of the memory, it can´t because the self.window.rootViewController still holds a reference to it? Or am I completely wrong?
If you just look at the direction of the arrows, you can see that there's no cycle here. The view controller will simply have two owners.
If you want to deallocate self.viewController you would need to clear both references:
self.window.rootViewController = nil; // or some other controller
self.viewController = nil; // or some other controller
Related
When I use a UIPopoverController and give it a contentViewController, I cannot seem to get the contentViewController to correctly be deallocated (as evidenced by the fact that the contentViewController.viewDidUnload is never getting called).
Code to create and display the popup:
PopupTestViewController *popupTest = [[PopupTestViewController alloc] initWithNibName:#"PopupTestViewController" bundle:nil];
popupTest.mainViewController = self;
self.popoverController = [[UIPopoverController alloc] initWithContentViewController:popupTest];
self.popoverController.popoverContentSize = popupTest.view.frame.size;
self.popoverController.delegate = self;
[self.popoverController presentPopoverFromRect:button.frame inView:button.superview permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
Now I am assuming that the contentViewController (in the code above, this is PopoverTestViewController) should be deallocated when the UIPopoverController is closed (whether by clicking off of it, or be explicitly dismissing it). But viewDidUnload is never called. I did notice, however, that if I define a dealloc method for PopoverTestViewController, that is called appropriately.
So my question is: why is viewDidUnload never getting called?
(And I'm using ARC).
viewDidUnload is not guaranteed to get called. It only gets called when the application receives a memory warning and the receiving ViewController has view loaded but is off screen.
When the view is loaded and retain count reaches zero, viewDidUnload is not called.
You can find more details in the documentation.
Instructions on when to release what objects can be found in the same document.
This is a little different than I've used UIPopoverController. Since you manually alloc'ed popupTest, you definitely need to manually release it. The UIPopoverController instance will retain popupTest when initWithContentViewController: is called.
Furthermore, if you are defining the popoverController property with retain, then you're getting a double-retain when you use the self.popoverController setter by assigning directly from the alloc. The general pattern for setting a #property is:
#property (nonatomic, retain) Foo* foo;
...
Foo* aFoo = [[Foo alloc] init];
self.foo = aFoo;
[aFoo release];
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.
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 just installed Xcode 4 and opened an earlier version of my app. The analyzer is reporting for this line:
[self.myViewControllerObject release];
incorrect decrement of the reference count of an object that is not owned at this point by the caller
I didn't enable ARC for my project.
When I analyze v2.0 of my app in Xcode 3.2.5, it doesn't show any potential error.
Header:
#class MyViewController;
MyViewController *myViewControllerObject;
#property ( nonatomic , retain ) MyViewController *myViewControllerObject;
Implementation:
#import "MyViewController.h"
#synthesize myViewControllerObject;
When a button is clicked I have:
TRY 1:
self.myViewControllerObject = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
[self.myViewControllerObject release];
TRY 2:
MyViewController *temp = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
self.myViewControllerObject = temp;
[temp release];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
[self.myViewControllerObject release];
TRY 3:
self.myViewControllerObject = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
In the dealloc method, I release it:
[self.myViewControllerObject release];
The warning comes from you calling release on a property through the accessor: when you do [self.myViewControllerObject release] you are actually calling the accessor method myViewControllerObject and then release on the return value. Since the name of the method does not begin with new, copy, or mutableCopy, you do not own the object it returns, hence you are not “allowed” to release it.
The solution is to never call release on the return value of that accessor, so basically your try #2 was fine:
MyViewController *temp = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
self.myViewControllerObject = temp;
[self.navigationController pushViewController:temp animated:YES];
[temp release];
But in dealloc do not use the accessor, rather:
[myViewControllerObject release];
If you need to release myViewController other than in dealloc, assign nil through the setter:
self.myViewControllerObject = nil;
Edit: For more on the subject, see Apple's Advanced Memory Management Guide.
The navigation controller manages the lifecycle of view controllers you add to it. In this way, when you push the view controller, you are passing 'ownership'. However, there's no reason you can't also maintain a reference to the view controller. Try this order:
self.myViewControllerObject = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.myViewControllerObject release];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
Alloc'd objects start with a retain count of one. Assigning a value to your 'retain' property increments the retain count by one. You should only keep one retain for the object, so you correctly release. But since you're starting at two, it's fine to release it before you pass it to the navigation controller. The compiler warning you see is just that - a warning, not always evidence you are doing something strictly wrong. It shouldn't matter if you pass it then release, but the order above should avoid it.
As I used the XCode 4 Beta version, the problem occurred. But when I tried it in the XCode 4 version, the Analyzer warning didn't occur to me. I Thank you to all whoever participated to help me.Thank you for your time.
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];