UIPopoverController's contentViewController.viewDidUnload not getting called - objective-c

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];

Related

Does this code cause a retain cycle?

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

call a method after the object is released?

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;

Analyzer warning about incorrect decrement of reference count

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.

Dealloc Being Called Twice?

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];

Protocol Memory

I'm sorry for repost. What have really bug me is if property retain should release when:
case 1 : (code below) button is already alloc in init: then add to subview then release it, and by any chance I set the button property in another class (I didn't release in dealloc:) it will leak then?
case 2 : button is already alloc in init: then add to subview then release it, and by any chance I didn't set the any button property in another class (I didn't use the property) (I release it in dealloc) then it will crash.
So what should I do if I want to alloc button in init: and I want to set the property too ?
#interface SomeClass : UIView {
UIButton *_button;
}
#property (nonatomic, retain)UIButton *button;
#implementation SomeClass
#synthesize button = _button;
- (id)init {
_button = [[UIbutton alloc] initWithFrame:CGRectMake(0.0f,0.0f,100.0f,20.0f)];
[[_button titleLabel] setFont:BUTTON_FONT];
[_button setBackgroundImage:[[UIImage imageNamed:#"button_blue.png"] stretchableImageWithLeftCapWidth:20.0f topCapHeight:15.0f] forState:UIControlStateNormal];
[_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_button setTitleShadowColor:[UIColor blackColor] forState:UIControlStateNormal];
[[_button titleLabel] setShadowOffset:CGSizeMake(0.0f, 1.0f)];
[_button addTarget:self action:#selector(buttonDidTapped:) forControlEvents:UIControlEventTouchUpInside];
[self addSubView:_button];
[_button release];
}
- (void)dealloc {
//[self.button release]; // case 1
[self.button release]; // case 2
[super dealloc];
}
#end
So what should I do if I want to alloc button in init: and I want to set the property too ?
Here's how your code should look:
#implementation SomeClass
#synthesize button = _button;
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
UIButton *button = [[UIbutton alloc] initWithFrame:CGRectMake(0.0f,0.0f,100.0f,20.0f)];
// Setup the button here...
[self addSubView:button];
self.button = button;
[button release];
}
return self;
}
- (void)dealloc {
[_button release];
[super dealloc];
}
#end
Changes I made:
initWithFrame: is the designated initializer of the UIView, so that's the init method you've got to override
Always check to make sure that your superclass initialized successfully before you setup your class
You've got to return self at the end of your init statements. I don't think your code would have compiled as written.
You created a property, you should use it. Use a temp variable to do all the setup for your button, then when you're finished with setup, use the property accesssor to set the variable and release your temp variable.
Because you only use the property to get/set your button, when it's time to dealloc you can guarantee that the _button iVar will either be valid or nil. Either way calling release on that variable is OK.
I don’t understand what you mean by “using it for read-only”. As declared, the property is read-write both for the class itself and from the outside. But the question can be reasonably answered nevertheless – once your class retains some object, it must release it when it gets deallocated.
P.S. I think you can safely drop the underscore prefix for private variables, it serves no real purpose and makes you write more code. In fact, with modern runtimes you can even drop the instance variable declaration:
#interface Foo : NSObject {}
#property(assign) BOOL bar;
#end
#implementation Foo
#synthesize bar;
#end
OK, third attempt: The problem is that you are in fact setting the property by assigning to the instance variable. Properties and instance variables are closely tied, when you give the following declaration…
#synthesize button = _button;
…you are saying that the _button instance variable should be used to store the value of the button property. Which means that your code over-releases, since you alloc the button in init (+1), release the button in init (-1) and then release again in dealloc (-1 again).
If you have not yet studied the Cocoa Memory Management Guide, do it. Even if you don’t plan to read any other documentation (which would be a pity), make an exception with this one, it will pay you back plenty.
sure, You should release it, because you have used retain for it.
In your -init method, you have a balanced retain/release call. So you don't need to release it again. But by doing it, you are sacrificing the reliability of the value held by _button. If somewhere down the lane the button is removed from the subviews, the button's retainCount could hit zero and it can be deallocated. Then the value held by _button is garbage. So you should not release _button in -init and rather you should do that in -dealloc.
Now, if you access the property elsewhere (outside this UIView object), you don't need to release it again unless you retain it there.
Try to replace [self.button release]; with [self.button removeFromSuperview];