7

Being fairly new to iPhone / Objective-C development, I wanted to ask this question to make sure that I'm going about initializing instance variables correctly in different scenarios. So below, I'm going to present a few scenarios and if anyone sees anything being done incorrectly, can they please let me know. (Note: For my examples I'll be using "instanceVariable" for the instance variable we're looking to initialize, which is an object of class "InstanceVariableClass".)

Scenario 1: Initializing in a Non UIViewController Class

a) New Allocation

- (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) {
        instanceVariable = [[InstanceVariableClass alloc] init];
    }
    return self;
}

In the initializer, it's OK to access the variable directly (ie not through it's property) and allocate it. When you call alloc, the newly created object will be automatically retained, which will work perfectly later on when you use it with your getter and setter methods. You don't want to allocate the variable using a property, i.e. self.instanceVariable = [[InstanceVariableClass alloc] init]; or else you'll be retaining it twice (once in your setter method, and one with the alloc).

b) Parameter

- (id)initWithFrame:(CGRect)frame object(InstanceVariableClass*) theInstanceVariable {

    self = [super initWithFrame:frame];
    if (self) {
        instanceVariable = [theInstanceVariable retain];
    }
    return self;
}

Once again, OK to directly access your instance variable in the initializer. Since you're not allocating the variable and simply are wanting to own a copy that was passed into you, you need to have it explicitly retain itself. If you had used your setter method, it would've retained it for you, but want to avoid accessing properties in the initializer.

c) Convenience Method

- (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) {
        instanceVariable = [[InstanceVariableClass returnInitializedObject] retain];
    }
    return self;
}

When using a convenience method to return a new object, you also need to explicitly retain for the same reasons as a parameter. The convenience method (if implemented properly) will have autoreleased the new object it generates, so we don't have to worry about doubly retaining it.

Scenario 2: Initializing in a UIViewController Class

a) New Allocation

- (void) viewDidLoad // or - (void) loadView if you implemented your view programmatically
{
    [super viewDidLoad];

    InstanceVariableClass *tempInstanceVariable = [[InstanceVariableClass alloc] init];
    [self setInstanceVariable: tempInstanceVariable];
    [tempInstanceVariable release];
}

In a UIViewController, you want to do your instance variable initializing in the viewDidLoad method to employ the practice of lazy loading, or loading in your variables only at the exact moment you need them. Outside of the initializer, it's bad practice to access the variable directly, so we'll now use our synthesized setter method to set the variable. You don't want to allocate the variable using the setter method, ie [self setInstanceVariable] = [[InstanceVariableClass alloc] init]; or else you'll be retaining it twice (once in your setter method, and one with the alloc). So the best practice is to create a new temp variable, initialize the temp variable, set your instance variable to the temp variable, then release the temp variable. The synthesize setter method will have retained the variable for you.

b) Convenience Method

- (void) viewDidLoad // or - (void) loadView if you implemented your view programmatically
{
    [super viewDidLoad];

    [self setInstanceVariable: [InstanceVariableClass instanceVariableClassWithInt:1]];
}

Initializing an instance variable outside of an initializer method, we can simply use our setter method to set and retain the object that is generated. The convenience method (if implemented properly) will have autoreleased the object it returns, so we don't have to worry about doubly retaining it.

That's what I have so far. If anyone can find any flaws in my reasoning, or think of any other scenarios I forgot to include, please let me know. Thanks.

Ser Pounce
  • 14,196
  • 18
  • 84
  • 169

4 Answers4

3

1a) Pefect, apart from this bit:

call retain on itself automatically

instanceArray does not retain itself - it's just an assignment to raw memory reserved for your instance.

One critical part that you got right, which many people overlook, is that you should avoid using the accessors in partially constructed/destructed states. The reason is not just reference counting, it's also proper program flow in these states.

1b) It's extremely rare (for me) to declare an NSArray property as retain - you should use copy instead. Your initializer should agree with the semantics of your property, so in most cases, you would change that to instanceArray = [parameterArray copy];.

1c) Looks good, but you should also consider the points I made at 1a and 1b.

2) Well, it really depends. Lazy initialization is not always the best. There are some cases where it will be better to initialize your ivars in the initializer, and some when the view's loaded. Remember that your vc may be unloaded, and that it's quite typical to destroy what you create when loading. So, there really isn't a hard and fast rule - if something takes time to create or must it persist in the event your vc is reloaded, it may be more logical to handle that in the initializer. The examples look good, when lazy initialization is preferable.

Community
  • 1
  • 1
justin
  • 104,054
  • 14
  • 179
  • 226
  • I just got rid of the arrays because that was besides the point and put in some variable "instanceVariable" of class "InstanceVariableClass". Also, replaced the line you were talking about with "When you call alloc, the newly created object will be automatically retained". Does that make more sense? – Ser Pounce Nov 10 '11 at 07:27
  • @CoDEFRo Ok. It's a little more correct to say (something along the lines of): "When you are returned an object from its initializer you are responsible for releasing it when you are finished with it. The instance we created and stored to this ivar will be sent its matching release when either the setter is called, or in `dealloc`". An instance's `alloc` should be paired with its initializer. – justin Nov 10 '11 at 08:07
  • It's worth mentioning that point 1a in your answer is a hotly debated topic. There's a lot of disagreement about whether or not property accessor methods should be used in init/dealloc. My personal opinion is the exact opposite of what you're recommending - variables should always be accessed by their property, even inside init/dealloc, and an object should understand enough about itself to do so in a safe manner. – Abhi Beckert Nov 10 '11 at 08:55
  • @Abhi I know there are people with that preference, but preference should not outweigh program correctness, maintainability, good design, or keeping within the realm of defined behaviour. Perhaps you'll change your mind after it's troubled you enough (as I did years ago). – justin Nov 10 '11 at 10:20
2

All the examples you've provided are perfectly valid.

However many experienced obj-c programmers prefer to never access instance variables directly except for inside their set/get methods (which may not even exist if you're declaring them with @property and @synthesize) unless it's necessary to avoid some performance bottleneck.

So, my constructors normally look something like this:

- (id)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self) {
    self.instanceArray = [NSArray array];
  }
  return self;
}

But I will sometimes choose to write my code exactly as you have done, if profiling the code shows the set/get methods and autoreleass pools are taking up too much CPU time or RAM.

Abhi Beckert
  • 32,787
  • 12
  • 83
  • 110
0

First, Objective-C doesn't have class variables; just instance variables.

Secondly, you are way over-thinking this. The rules of memory management are relatively simple and orthogonal to setter/getter methods and/or object creation. Using the setter in a -init* method is an issue in that you may trigger side effects if the setter is overridden. However, if you have setter/getter side-effects in play that screw up during -init* and -dealloc, then you likely have far worse architectural issues in play.

  1. If you +new, +alloc, -retain, or -copy [NARC] an object, you need to -release it somewhere or it'll stick around (and, likely, leak).

  2. If a setter wants to keep a hold of an object, it'll either -retain or -copy it (as appropriate) and that is it's business to balance the -retain. Externally to the setter you shouldn't care.

  3. autorelease is nothing more than a per-thread delayed release. While, generally, you don't need to worry about autoreleased objects created via the various convenience object instance creation methods, autorelease pressure can be a real performance problem in certain circumstances and using an explicit +alloc / set / -release can be useful.

This is all explained in detail in the Objective-C memory management guide.


Think of it this way:

  • when you make a direct assignment to an iVar, you are not leaving the calling scope and, thus, the assignment can consume the +1 retain count being maintained (possibly) in the calling scope.

  • when you assign through a method call (dot syntax or otherwise), the retain count being maintained in your calling scope is irrelevant to what happens in that setter method. The two are required to maintain their respective retain count deltas independently. That is, if the setter wants to keep the object around, it'll retain it. The caller maintains it's retain count independently.

bbum
  • 162,346
  • 23
  • 271
  • 359
  • you can have static variable that are kind of class variables. Just think of a singleton. – Vincent Bernier Nov 10 '11 at 06:38
  • Thanks for that. Yes I understand all these principles, but I was looking at a specific subset, ie when you're initializing variables which has some special cases that you normally don't see, ie should you directly access the variable in the initializer or use property, or how to go about initializing in a viewcontroller. – Ser Pounce Nov 10 '11 at 06:41
  • VinceBurn: A static variable is accessible to entire scope of declaration and can span multiple classes (if multiple @implementations exist within a single compilation unit). – bbum Nov 10 '11 at 16:46
  • @CoDEFRo My point was that your "special cases" only exist because you are colluding concepts that are otherwise orthogonal. If you follow the NARC rule, nothing else matters. That a `retain` happens on property assignment should be irrelevant to your calling scope. – bbum Nov 10 '11 at 16:49
0

Senario 1 a)
This is useless code. NSArray is immutable, so once created it can't be changed. so instead do this  instanceArray = nil; or event better do self.instanceArray = nil;
If instanceArray was a NSMutableArray it would make sense to alloc it there, but since it's not, it's a waste.

1b) if your property is set to (retain) use it insted self.instanceArray = parameterArray

1c) THIS IS NOT A CONVENIENCE METHOD. Convenience method are usually class method that return autorelease object.
And the code your showing there, well I'm sure it's not compiling.

Senario 2a)
Same answer as 1a)

same answer as 1c)

As Much as possible use your property. So if you've got variable that much be kept in sync, it's easier to do that way.
And make sure to understand the difference between NSArray and NSMutableArray. (or any other class that have mutable and immutable version)


As for the difference between a UIViewController and a non UIViewController.
(Ok they can, but they are nil at that point)

IBOutlet can't be accessed in the init method. So they must by initialize later.

So in general the data side should be done in the init, view costomization in code should go into viewDidLoad, and lastMinute logic and / or refresh should go into viewWillAppear.
Remember that viewWillAppear will be call each time the view is about to appear, including when coming back from lower in the UIViewController hierarchy.

This are guide lines, and as all guide lines, you sometimes need to bend the line a little bit.

Vincent Bernier
  • 8,674
  • 4
  • 35
  • 40
  • OK I corrected the NSMutableArray thing, though that was really besides the point of my example. Yes I know you should use a property, but some people say that you shouldn't use it in an initializer. – Ser Pounce Nov 10 '11 at 06:46
  • I don't see why, the only exception I can think would be in an initWithCoder method while implementing an NSCoding protocol and still it could be debatable. Accessor method are there so you can have a unique access point to your variable. It's helping you have more sustainable code. – Vincent Bernier Nov 10 '11 at 07:00