Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing instance variables in iPhone Development / Objective-C

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.

like image 879
Ser Pounce Avatar asked Nov 10 '11 06:11

Ser Pounce


2 Answers

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.

like image 151
justin Avatar answered Sep 29 '22 03:09

justin


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.

like image 45
Abhi Beckert Avatar answered Sep 29 '22 03:09

Abhi Beckert