RE: UIViewController memory warnings | didReceiveMemoryWarning |

  • 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