Re: UIViewController memory warnings | didReceiveMemoryWarning | setView | releasing outlets | releasing properties
Re: UIViewController memory warnings | didReceiveMemoryWarning | setView | releasing outlets | releasing properties
- Subject: Re: UIViewController memory warnings | didReceiveMemoryWarning | setView | releasing outlets | releasing properties
- From: Ashley Clark <email@hidden>
- Date: Sat, 29 Nov 2008 01:38:28 -0600
On Nov 28, 2008, at 11:44 PM, Glenn Bloom wrote:
In the course of trying to understand UIViewController memory
warnings on
the iPhone, I've found various useful threads online, and in
particular, was
very glad to follow the numerous recent posts in this forum with the
subject
'Outlets / IBOutlet declarations'.
In response, I've written a test app to confirm what I think I have
gathered
so far. I will be very appreciative if anyone can take a look at the
interface and implementation below and respond to any of the five (5)
different comments tagged with "MUST CONFIRM". I have also heavily
commented most everything else related to memory management (gone
overboard), thinking it relevant to this example, at least for folks
like
myself, newer to the subject.
Essentially, what is below are an interface and implementation that
modify
the iPhone View-Based Application template to display a UILabel and a
UIImage within a couple of nested views. I define and work with
different
instance variables and (local variables as well), solely for the
point of
trying to compare what gets released/deallocated where and how...
Thanks to anyone in advance for any assistance.
I'll take a stab at it.
//
// Test00ViewController.h
// Test00
//
#import <UIKit/UIKit.h>
@interface Test00ViewController : UIViewController {
NSString *myStringA; // Will NOT be a property
NSString *myStringB;
NSString *myStringC; // Will NOT be a property
NSString *myStringD;
IBOutlet UIView *primaryViewA;
IBOutlet UIView *subViewA;
IBOutlet UILabel *labelA;
IBOutlet UIImageView *imageViewA;
}
@property (nonatomic, retain) NSString *myStringB;
@property (nonatomic, retain) NSString *myStringD;
// Note: For the iPhone, unless encountering a compelling reason not
to do so, generally make outlets properties, and retain them.
@property (nonatomic, retain) UIView *primaryViewA;
@property (nonatomic, retain) UIView *subViewA;
@property (nonatomic, retain) UILabel *labelA;
@property (nonatomic, retain) UIImageView *imageViewA;
@end
I think the suggested syntax here is to move the IBOutlet tag to the
@property line and out of the @interface section as well, for example
@property (nonatomic, retain) IBOutlet UIView *someView;
//
// Test00ViewController.m
// Test00
//
#import "Test00ViewController.h"
@implementation Test00ViewController
@synthesize myStringB;
@synthesize myStringD;
@synthesize primaryViewA;
@synthesize subViewA;
@synthesize labelA;
@synthesize imageViewA;
// Implement loadView if you want to create a view hierarchy
programmatically
- (void)loadView {
You're leaking strings here.
NSString* string00 = [[NSString alloc] init];// local variable that
will
need to be released locally
At this point, string00 is an empty string that you'll need to release.
string00 = @"00";
After this line, string00 is now pointing to a new string object and
you've leaked the previous empty string object.
[string00 release];
String constants, like @"00", are specially generated by the compiler
as static objects; release and retain have no effect on them.
NSString* string01 = [NSString string];// local variable set with a
constructor that handles release, so there will be no need to
release it
string01 = @"01";
myStringA = [[NSString alloc] init];// instance variable that will
need to be released in dealloc
myStringA = @"A";
Same thing applies for string01 and myStringA here. Also it might be
important to point out that setting your myStringA properties in this
way bypasses your @synthesized accessors. In every method except for -
init and -dealloc you should be accessing your properties via
self.propertyName.
So your myStringA assignment should look like this:
self.myStringA = @"A";
myStringB = [NSString stringWithFormat:@"B"];// instance variable -
must be released in dealloc, even though set with a constructor that
handles release
myStringC = [NSString stringWithFormat:@"C"];// instance variable -
must be released in dealloc, even though set with a constructor that
handles release
myStringD = [NSString stringWithFormat:@"D"];// instance variable -
must be released in dealloc, even though set with a constructor that
handles release
In this case, since you're not using your accessor methods, you are
not retaining these objects and when the autorelease pool is drained
you'll be left with dangling references. Use your accessors.
NSMutableString * stringEMutable = [NSMutableString
stringWithString:string00]; // local variable set with a constructor
that handles release, so there will be no need to release it
[stringEMutable appendString: @", "];
[stringEMutable appendString: string01];
[stringEMutable appendString: @", "];
[stringEMutable appendString: myStringA];
[stringEMutable appendString: @", "];
[stringEMutable appendString: myStringB];
[stringEMutable appendString: @", "];
[stringEMutable appendString: myStringC];
[stringEMutable appendString: @", "];
[stringEMutable appendString: myStringD];
printf("contents of stringEMutable: %s\n", [stringEMutable
UTF8String]);
If you use NSLog to print to the console you can print objects with
the %@ format tag. This has the effect of calling -description on the
object and printing that string. For NSString objects -description
returns the string value itself.
primaryViewA = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]
applicationFrame]]; // instance variable - will be released in dealloc
primaryViewA.backgroundColor = [UIColor redColor];
subViewA = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]
applicationFrame]]; // instance variable - will be released in dealloc
subViewA.backgroundColor = [UIColor greenColor];
This is a tricky part here. You should be setting it via your
synthesized accessors but since [UIView alloc] init...] returns an
object that you already own, setting it via self.primaryViewA will
retain it again, meaning that when your dealloc releases it you'll
still have one more object retaining it unless you release it here.
The same applies for subViewA.
Ideally you should allocate it to a temporary variable, assign that to
your @property and then release your temporary variable.
UIView *view = [[UIView alloc] init...];
self.primaryViewA = view;
[view release];
I'm a little confused why you're defining IBOutlets though and then
creating them programmatically. Typically an IBOutlet specifies an
instance variable that references some object that is defined in your
NIB file.
labelA = [[UILabel alloc]init];// instance variable - will be
released in dealloc
CGRect rectA = CGRectMake(0,0, 320,50);// local variable set with a
constructor that handles release, so there will be no need to
release it
Technically a CGRect is a scalar structure that's local to the scope
it's defined in. It has no concept of retain/release/autorelease, in
the same way that NSInteger, NSUInteger, BOOL and CGFloat are.
labelA = [[UILabel alloc] initWithFrame:rectA];
labelA.font = [UIFont systemFontOfSize:12.0];
labelA.textAlignment = UITextAlignmentCenter;
labelA.text = stringEMutable;
labelA.textColor = [UIColor whiteColor];
labelA.backgroundColor = [UIColor blueColor];
[labelA setText:stringEMutable];
[subViewA addSubview:labelA];
More of the same as before with labelA here.
CGRect imageRect;
UIImage *theImage;
theImage = [UIImage imageNamed:@"Button00A.png"]; // local variable
setwith a constructor that handles release, so there will be no need
to release
it
int w = theImage.size.width;
int h = theImage.size.height;
imageRect = CGRectMake(50.0, 50.0, w,h);// local variable set with a
constructor that handles release, so there will be no need to
release it
imageViewA = [[UIImageView alloc]initWithFrame:imageRect]; //
instance variable - will be released in dealloc
imageViewA.backgroundColor = [UIColor clearColor];
imageViewA.image = theImage;
[subViewA addSubview:imageViewA];
[primaryViewA addSubview:subViewA];
self.view = primaryViewA;
}
/* snip */
// Method setView:
// Overrides setter for UIViewController property view.
- (void)setView:(UIView *)theView;
{
if (theView == nil){
// release outlets when the argument is nil
// As long as this UIViewController subclass retains its top level
view
then all of the view's subviews will also be retained. However,
they should
all be released when the UIViewController releases its view... And
we can't
release the outlets in method didReceiveMemoryWarning because... 1.
MUST
CONFIRM: we have declared our outlets as properties, with "retain",
and we
can't determine accurately within didReceiveMemoryWarning when the
view
controller's view is in fact released (except by calling setView),
so we
can't conditionally release them within the didReceiveMemoryWarning
method
(except by actually setting the controller's view).
self.labelA = nil;
self.imageViewA = nil;
self.subViewA = nil;
self.primaryViewA = nil; // 2. MUST CONFIRM: We also release this
outlet
here, not in didReceiveMemoryWarning, despite the fact that the
controller's
view is set to this rather than it be added to the controller's view
as a
subview.
}
[super setView:theView];
}// End Method setView:
Someone else will have to comment on these two specifically. I can say
that since you have retained these objects though you need to release
them if you are no longer interested in them here. So, since you
retained primaryViewA in one instance variable and then assigned it to
your superclass' view property, you really have two references to it.
The setView:nil method that you're overriding here by default only
releases one of those references.
- (void)didReceiveMemoryWarning {
// Release anything that's not essential, such as cached data
(meaning instance variables, and what else...?)
// Obviously can't access local variables such as defined in method
loadView, so can't release them here
// We can set some instance variables as nil, rather than call the
release
method on them, if we have defined setters that retain nil and
release their
old values (such as through use of @synthesize). This can be a better
approach than using the release method, because this prevents a
variable
from pointing to random remnant data. Note in contrast, that
setting a
variable directly (using "=" and not using the setter), would result
in a
memory leak.
self.myStringB = nil;
self.myStringD = nil;
[myStringA release];// No setter defined - must release it this way
[myStringC release];// No setter defined - must release it this way
Even if you haven't defined setters for these objects I'd still
recommend setting them to nil when you release them for precisely the
same reason that you set the properties to nil. It can even be done on
one line.
[myStringA release], myStringA = nil;
/* 3. MUST CONFIRM: NOT necessary to release outlets here - See
override of
setView instead.
self.labelA = nil;
self.imageViewA = nil;
self.subViewA = nil;
*/
// Releases the view if it doesn't have a superview
[super didReceiveMemoryWarning];
}
- (void)dealloc {
// We can set some instance variables as nil, rather than call the
release
method on them, if we have defined setters that retain nil and
release their
old values (such as through use of @synthesize). This can be a better
approach than using the release method, because this prevents a
variable
from pointing to random remnant data. Note in contrast, that
setting a
variable directly (using "=" and not using the setter), would result
in a
memory leak.
self.myStringB = nil;
self.myStringD = nil;
[myStringA release];// No setter defined - must release it this way
[myStringC release];// No setter defined - must release it this way
While UIViewController uses self.view = nil (or [self setView:nil]) in
its' dealloc, this is not the recommended way to release your retained
objects in your -dealloc method. Since a property access is still just
a method call it may have unwanted side-effects that you may not even
be aware of, think subclasses, you should therefore call release
directly on any retained objects you may have, regardless of their
status as properties or not.
// A caveat to the choice illustrated above (setting an instance
variable as
nil versus using the release method)... Because UIViewController
currently
implements its dealloc method using the setView: accessor method
(rather
than simply releasing the variable directly...), self.anOutlet = nil
will be
called in dealloc as well as in response to a memory warning... This
will
lead to a crash in dealloc. The remedy is to ensure that outlet
variables
are also set to nil in dealloc as follows:
// [anOutlet release], anOutlet = nil;
[labelA release], labelA = nil; // rather than: self.labelA = nil;
[imageViewA release], imageViewA = nil; // rather than:
self.imageViewA =
nil;
[subViewA release], subViewA = nil; // rather than: self.subViewA =
nil;
[primaryViewA release], primaryViewA = nil; // rather than:
self.primaryViewA = nil; ... 4. MUST CONFIRM: Not sure if this need
to be
explicitly released or if it will be released when the
ViewController's view
is automatically released (because the ViewController's view was set
to it)?
Since you have a retained reference to it, you must release it.
// 5. MUST CONFIRM: Don't need to explicitly release the
ViewController's
view.
Your superclass retained the view when you set it (or loaded it from a
NIB file); the superclass is responsible for it, not you.
[super dealloc];
}
@end
I definitely recommend reading over the Memory Management guidelines
again since you've made quite a few mistakes with what you have and
haven't retained.
http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html
Ashley
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden