Alexei Sholik
Alexei Sholik

Reputation: 7469

Object initialization sequence in Objective-C

The Cocoa framework has a convention to always call self = [super init] in the init method of an inherited class, because [super init] may return a new instance.

What will happen if I do this?

@interface MyClass : NSObject /* or any other class */ {
    int ivar_;
}
@end

@implementation MyClass

- (id)init {
    ivar_ = 12345;

    if ((self = [super init])) {
        NSLog(@"ivar_'s value is %d", ivar_);
    }
    return self;
}

@end

In the case when [super init] returns a new instance, what will I see in the console? ivar_'s value is 0?

I can't think of a way to check this myself, because I don't know which class may return a new instance from its init method. Also, can't seem to find explicit clarification for this scenario in the docs.

Could anyone help me out? Thanks!

Upvotes: 5

Views: 1324

Answers (1)

user557219
user557219

Reputation:

Under normal circumstances, when you send +alloc to a class it returns a zeroed out instance of that class. It is already initialised in the sense that: a) it is a proper instance of that class, and b) all instance variables are zeroed out (numeric types are 0, C pointers are NULL, objects are nil, etc). However, no extra initialisation behaviour has been applied to the newly created instance — that’s the job of initialisers, -init being the most common of them. In particular, if your class inherits from NSObject then [super init] doesn’t touch the newly created instance. So, in the code you’ve posted in your question,

  1. (outside the class) +alloc is sent to MyClass, which returns a zeroed out instance. In particular, ivar_ is 0
  2. (outside the class) -init is sent to the newly created instance
  3. ivar_ == 12345 because of the assignment in -init, which could be rewritten as self->ivar_ = 12345. At this moment, self points to the instance returned by +alloc
  4. [super init] is sent, ivar_ isn’t touched since its superclass (NSObject) doesn’t know about it, and the return value is assigned to self. In fact, the return value is already self so nothing’s changed
  5. Since self is different from nil, NSLog() is called
  6. -init returns self

Now let’s consider that [super init] returns an instance that’s different from the one returned by +alloc:

  1. (outside the class) +alloc is sent to MyClass, which returns a zeroed out instance. In particular, ivar_ is 0
  2. (outside the class) -init is sent to the newly created instance
  3. ivar_ == 12345 because of the assignment in -init, which could be rewritten as self->ivar_ = 12345. At this moment, self points to the instance returned by +alloc
  4. [super init] is sent and it decides to release the current instance and return a different one. The return value is assigned to self so, from the current perspective, -init is now working on a different instance. The old instance was deallocated, hence that change to ivar_ is lost. ivar_ contains whatever [super init] decides it should contain
  5. Since self is different from nil, NSLog() is called
  6. -init returns self, which is not the same instance created by +alloc

You can test this with the following code. Be warned that it’s just a quick example to illustrate my answer and it should not be used in production code.

#import <Foundation/Foundation.h>

@interface MySuperClass : NSObject
@end

@interface MyClass : MySuperClass
{
  @public
  int ivar_;
}
@end

@implementation MySuperClass

static MyClass *defaultInstance;

- (id)init
{
  if ([self isMemberOfClass:[MyClass class]] && defaultInstance != nil)
  {
    [self release];
    return defaultInstance;
  }

  return [super init];
}
@end

@implementation MyClass
- (id)init
{
  ivar_ = 12345;

  if ((self = [super init]))
    NSLog(@"ivar_'s value is %d", ivar_);

  return self;
}
@end

int main()
{
  NSAutoreleasePool *pool = [NSAutoreleasePool new];

  defaultInstance = nil;
  defaultInstance = [[MyClass alloc] init];
  NSLog(@"%d", defaultInstance->ivar_); // outputs 12345

  defaultInstance->ivar_ = 98765;
  NSLog(@"%d", defaultInstance->ivar_); // outputs 98765

  MyClass *someInstance = [[MyClass alloc] init];
  NSLog(@"%d", someInstance->ivar_); // outputs 98765

  if (someInstance == defaultInstance)
    NSLog(@"They're the same!");

  [pool drain];
  return 0;
}

The output is

ivar_'s value is 12345
12345
98765
ivar_'s value is 98765
98765
They're the same!

Upvotes: 5

Related Questions