Adding subviews to a view programmatically: where should memory be managed?
I've read http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/BasicViewControllers/BasicViewControllers.html#//apple_ref/doc/uid/TP40007457-CH101-SW19 but I'm still not completely clear on how this works.
@property (nonatomic, retain) UIButton *startButton;
@property (nonatomic, retain) UITextView *infoTextView;
This is a view controller displayed in a tab bar
- (void)loadView
{
UIView *newView = [[UIView alloc] init];
//self.startButton and addSubivew retains the button obect; retain count = 2
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
//autorelases
self.startButton = button;
[newView addSubview:startButton];
//addSubview retains infoTextView; self.infoTextview retains; retain count: 2
self.infoTextView = [[[UITextView alloc] initWithFrame:CGRectMake(0.0, 0.0, 280.0, 270.0)] autorelease];
//autoreleased
[newView addSubview:infoTextView];
//View controller retains the view hierarchy
self.view = newView;
[newView release];
}
//customization of the button and textview (text, frame, center, target-action etc)
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadStartButton];
[self loadTextView];
}
//because these have retain properties, they are released through nil
- (void)viewDidUnload
{
self.startButton = nil;
self.infoTextView = nil; //retain counts - 1
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc
{
[startButton release];
[infoTextView release]; //retain counts -1
[super dealloc];
}
My question is this: should the UIView objects have retain counts of two? The way I see it is that the view controller retains the UIView object, and the viewController.view also retains the UIView obj开发者_StackOverflow中文版ect (through adding subviews). Is this the correct way to look at it conceptually? Because then my viewController is also managing objects owned by its .view property.
However, I'm not sure if both viewDidUnload and dealloc are called in the case of low memory situations. Am I releasing them correctly or setting up a memory leak?
(any comments about putting code in the wrong place would be helpful as well)
In a low-memory situation, I don't think your view controller will be dealloc'ed. Your view will. viewDidUnload will be called but not dealloc. That's ok. Your buttons will be freed. Both the reference from the view controller will be released, by your viewDidUnload code, and the reference from addSubview, by the view's default dealloc code (or somewhere in default code).
When you do deallocate your view controller, if ever, both will get called. The release statements in the dealloc will do nothing, because they will be equivalent to [nil release], which is a no-op. That's ok.
I think you can take the release statements out of the dealloc though, since dealloc should never get called without viewDidUnload getting called first.
It does look like your UIView objects will have retain counts of two. That's not necessarily a bad thing though. Your view could come and go due to low memory situations, and if you don't want to re-create the UI elements each time the view is loaded, having your view controller is a reasonable thing to do.
Typically, low memory conditions will cause your view to be unloaded so viewDidUnload will be called. The dealloc method of your view controller would not be called in most cases, unless all references to your view controller were somehow removed.
I typically have my viewDidUnload basically "undo" whatever happened in by viewDidLoad. As low memory situations occur, the view gets unloaded and the work done at load time gets undone. When the view gets reloaded it basically gets redone. You seem to be doing more than that here. Your viewDidUnload is undoing what was done in loadView. In this case, I think you are OK: When the view gets reloaded it should (I think) call both loadView. As a general rule though, I like to match up the viewDidLoad and viewDidUnload. It makes things more consistent with view controllers that don't implement loadView directly.
Conceptually I don't worry too much about the actual retain count. I try more to balance my calls. If I take a retain locally in a method then I will (most likely) need to call a release in the same method.
...
UIView *newView = [[UIView alloc] init]; <---- retain count gets +1
// ... do more stuff
[newView release]; newView = nil; <---- retain count gets -1
...
in the above the calls are balanced so we are good.
/*
* This creates an autoreleased button. Therefore I am not taking a retain on
* it so I don't need to release it
*/
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
//autorelases <---- this comment is redundant as the code states this
It works slightly different for ivars as generally you try to balance the calls in the init
and dealloc
methods and then use properties to do the right thing throughout your class.
/*
* As we have used sythesized properties the memory management is taken care
* for us if we use dot notation or call the setters/getters
*/
self.startButton = button;
OR
[self setStartButton:button];
/*
* Because this is an ivar and not a local method variable we balance our calls
* in the `init` (if we need to, remember ivars are initialized to nil for us)
* and `dealloc` methods.
*/
- (id)initWithButton:(UIButton *)startButton
{
self = [super init];
if (self) {
/*
* Notice we don't use self. You should try to access ivar
* directly in your init and dealloc methods to avoid side effects
*/
_startButton = [startButton retain];
}
return self;
}
- (void)dealloc
{
// Release any other ivars
/*
* Notice we don't use self. You should try to access ivar
* directly in your init and dealloc methods to avoid side effects
*/
[_startButton release];
[super dealloc];
}
// NOTE
// I have prefixed my ivars to make it more explicit when I am accessing them
// directly as opposed to accessing them through dot notation of normal method
// calls
Adding the view is not normally our concern as we trust the framework will manage its tasks memory correctly
/*
* We don't need to worry about this taking a +1 as our code is doing our
* management correctly
*/
[newView addSubview:startButton];
Memory Management Summary
- Local method variables - normally retain/release calls should be balanced within the method
- ivars - normally retain/release calls should be balanced with
retain
in theinit
andrelease
indealloc
and then always use properties throughout the class to manage all other memory tasks. - Don't rely on checking the retain count of objects just balance your memory management calls
viewDidUnload
Generally speaking you can see viewDidLoad
and viewDidUnload
as being a matched pair. Anything you create in viewDidLoad
is good to be freed in viewDidUnload
. You should not release anything in viewDidUnload
that can not be easily recreated check the docs for UIViewController.
Your view will only unload if it is not the currently visible screen and low memory levels conditions occur.
I think the question to ask is, will there ever be a case where your UIView
instances are removed as subviews but then preserved and re-added as subviews to some other view?
Because the way your code is implemented, it does not appear that this will be the case. And if that is true, then there is really no need for your UIViewController
to retain the views that it creates. It can simply add them as subviews, and then forget about them. You don't even need to have fields/properties for them, either. You can simply do:
- (void)loadView {
UIView *newView = [[UIView alloc] init];
//self.startButton and addSubivew retains the button obect; retain count = 2
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[newView addSubview:startButton]; //the button will be retained as long as it is part of 'newView'
//addSubview retains infoTextView; self.infoTextview retains; retain count: 2
UITextView* infoTextView = [[[UITextView alloc] initWithFrame:CGRectMake(0.0, 0.0, 280.0, 270.0)] autorelease];
[newView addSubview:infoTextView]; //the text-view will be retained as long as it is part of 'newView'
//View controller retains the view hierarchy
self.view = newView; //will be retained until we manually change the view or until we get unloaded/deallocated
[newView release];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self loadView];
}
//could just remove this now
- (void)viewDidUnload {
[super viewDidUnload];
}
//could just remove this now
- (void)dealloc {
[super dealloc];
}
As a general rule of thumb, you should prefer the simplest solution that does what you want. So if you can get away with not keeping any explicit reference to your programmatically-added subviews, that is what you should do.
Only keep explicit references to things that you intend to use for tasks other than just sending them a release
message at some point in the future. If that's all your doing, then you might as well send the release
immediately, and not keep the reference in the first place.
精彩评论