RE: UIViewController memory warnings | didReceiveMemoryWarning |
RE: UIViewController memory warnings | didReceiveMemoryWarning |
- Subject: RE: UIViewController memory warnings | didReceiveMemoryWarning |
- From: "Glenn Bloom" <email@hidden>
- Date: Sat, 29 Nov 2008 10:36:43 -0500
In response to an excellent first reply, below is a revision to my original
post that corrects various points, and that also incorporates some changes
to better focus my remaining questions - please disregard my original post
in favor of the following:
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 understand.
Below is 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), for the point of trying to compare what gets
released/deallocated where and how.., I am specifically trying to understand
how to override didReceiveMemoryWarning, setView and dealloc. Each point I
am most uncertain about is labelled with "MUST CONFIRM".
//
// 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
UIView *primaryViewA;
UIView *subViewA;
UILabel *labelA;
UIImageView *imageViewA;
}
@property (nonatomic, retain) NSString *myStringB;
// Note: For the iPhone, unless encountering a compelling reason not to do
so, generally make outlets properties, and retain them.
//Note: Typically, an IBOutlet specifies an instance variable that
references some object that is defined in your NIB file. However, just as
an academic exercise, in this example, primaryViewA will be created
programmatically, even though declared as an outlet.
// Preferred syntax is to use the IBOutlet tag on the @property line, rather
than in the interface declaration above
@property (nonatomic, retain) IBOutlet UIView *primaryViewA;
@property (nonatomic, retain) UIView *subViewA;
@property (nonatomic, retain) UILabel *labelA;
@property (nonatomic, retain) UIImageView *imageViewA;
@end
//
// Test00ViewController.m
// Test00
//
#import "Test00ViewController.h"
@implementation Test00ViewController
@synthesize myStringB;
@synthesize primaryViewA;
@synthesize subViewA;
@synthesize labelA;
@synthesize imageViewA;
// Implement loadView if you want to create a view hierarchy
programmatically
- (void)loadView {
NSString* string00Local = [[NSString alloc] initWithString:@"00"]; //Note:
String constants, like @"abc", are specially generated by the compiler as
static objects; release and retain have no effect on them. So there will be
no need to release string00Local
myStringA = [[NSString alloc] initWithString:@"A"];// Note that myStringA is
an instance variable but not a property, so can't call self.myStringA. Will
need to be released in dealloc
self.myStringB = [NSString stringWithFormat:@"B"]; // instance variable -
must be released in dealloc. And note that not using "self", alternatively
setting 'myStringB = ...' would be wrong - it would bypass your @synthesized
accessors. In every method except for -init and -dealloc, accessing your
properties via self.propertyName.
myStringC = [NSString stringWithFormat:@"C"];// myStringC is an instance
variable but not a property (so can't call self.myStringC).Will need to be
released in dealloc
NSMutableString * stringCompleteMutable = [NSMutableString
stringWithString:string00Local]; // local variable set with a constructor
that handles release, so there will be no need to release it
[stringCompleteMutable appendString: @", "];
[stringCompleteMutable appendString: myStringA];
[stringCompleteMutable appendString: @", "];
[stringCompleteMutable appendString: self.myStringB];
[stringCompleteMutable appendString: @", "];
[stringCompleteMutable appendString: myStringC];
// The property primaryViewA already has a retain count of 1, and if you
were to set it using [UIView alloc] init...] you would increase its retain
count to 2. Instead, allocate it to a temporary variable, assign that to the
@property and then release your temporary variable.
UIView *viewTempA = [[UIView alloc]initWithFrame:[[UIScreen mainScreen]
applicationFrame]];
self.primaryViewA = viewTempA;
[viewTempA release];
self.primaryViewA.backgroundColor = [UIColor redColor];
// See previous comment - same reasoning applies
UIView *viewTempB = [[UIView alloc]initWithFrame:[[UIScreen mainScreen]
applicationFrame]];
self.subViewA = viewTempB;
[viewTempB release];
self.subViewA.backgroundColor = [UIColor greenColor];
// See previous comment - same reasoning applies (use a temporary variable
here as well).
UILabel *labelTemp = [[UILabel alloc]init];
self.labelA = labelTemp;// instance variable - will be released in dealloc
[labelTemp release];
CGRect rectA = CGRectMake(0,0, 320,50);// CGRect is a scalar structure
that's local to the scope it's defined in. It has no concept of
retain/release/autorelease, the same as NSInteger, NSUInteger, BOOL and
CGFloat
self.labelA = [[UILabel alloc] initWithFrame:rectA];
self.labelA.font = [UIFont systemFontOfSize:12.0];
self.labelA.textAlignment = UITextAlignmentCenter;
self.labelA.text = stringCompleteMutable;
self.labelA.textColor = [UIColor whiteColor];
self.labelA.backgroundColor = [UIColor blueColor];
[self.labelA setText:stringCompleteMutable];
[self.subViewA addSubview:labelA];
CGRect imageRect;
UIImage *theImage;
theImage = [UIImage imageNamed:@"Button00A.png"]; // local variable set
with 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);
self.imageViewA = [[UIImageView alloc]initWithFrame:imageRect]; // instance
variable - will be released in dealloc
self.imageViewA.backgroundColor = [UIColor clearColor];
self.imageViewA.image = theImage;
[self.subViewA addSubview:imageViewA];
[self.primaryViewA addSubview:subViewA];
self.view = primaryViewA;
}
// Method setView:
// Overrides setter for UIViewController property view.
- (void)setView:(UIView *)theView;
{
if (theView == nil){
// release views and label 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 them in method didReceiveMemoryWarning because... 1. MUST CONFIRM:
we have declared them 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 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:
- (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;
// Even though no setters were defined for this object, still set it to nil
after releasing it for precisely the same reason that you set properties to
nil.
[myStringA release], myStringA = nil;
[myStringC release], myStringC = nil;
// Releases the view if it doesn't have a superview
[super didReceiveMemoryWarning];
}
- (void)dealloc {
// 3. MUST CONFIRM: No longer sure about this case...
// Original reasoning: 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.
// Versus...
// 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.
self.myStringB = nil;
[myStringA release];// No setter defined - must release it this way
[myStringC release];// No setter defined - must release it this way
// 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:
[primaryViewA release], primaryViewA = nil; // rather than:
self.primaryViewA = nil; ... And note that this does need to be explicitly
released; the ViewController's view was set to it, but it must still be
released separately
// 4. MUST CONFIRM: Correctly releasing the next three objects? They are
properties, but not outlets...
[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;
// Note don't need to explicitly release the ViewController's view - the
superclass will do this.
[super dealloc];
}
@end
_______________________________________________
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