Reputation: 14527
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.
Upvotes: 7
Views: 6416
Reputation: 162712
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.
If you +new, +alloc, -retain, or -copy [NARC] an object, you need to -release it somewhere or it'll stick around (and, likely, leak).
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.
autorelease
is nothing more than a per-thread delayed release
. While, generally, you don't need to worry about autorelease
d 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.
Upvotes: 0
Reputation: 104698
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.
Upvotes: 3
Reputation: 8664
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.
Upvotes: 0
Reputation: 33359
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.
Upvotes: 2