An Aside on Init's Consumption of Self

Alloc's Retain Semantics

Let's play!

 [SomeClass alloc];

You've probably not seen that line of code before. At least not alone.

The returned memory is uninitialized since it has not yet had an -init* method called. It has the minimum it needs to make it to the init method. This is nearly always a calloc'd region of memory large enough to hold an instance of the class with the isa field set (which class it is).

What is its retain count? Is it zero? Wouldn't that mean that it's deallocated? It can't be a zombie...

But can you release it? Let's take a look:

 NSLog(@"%lu", [[SomeClass alloc] retainCount]);

> 1

In retrospect, of course it is one. It *must* be one; no other number makes sense.

 [[SomeClass alloc] init];


What is its retain count?

If init retains self then the retain count would be 2. If init doesn't retain self then we have a very subtle and interesting potential leak.

Alloc and Init Can't *Both* Retain

Compiled with MRR (-fno-objc-arc)

 1 id temp = [SomeClass alloc];
 2 id obj = [temp init];
 3 ...
 4 [obj releasse];

Let us assume that init does NOT retain the object. Then in the case where temp != obj, who deallocated temp? If temp's init method returns a different object than temp then it is leaked!

Here's a case where the init method might return a different object.

Compiled with MRR (-fno-objc-arc)

 1 - (instancetype)init
 2 {
 3   [self release];
 4   self = [[SomeOtherClass alloc] init];
 5   return self;
 6 }

This method clearly releases the initial memory and so the leak is avoided. But we haven't actually yet been able to determine whether a retain would come from SomeOtherClass's init method. Even if it doesn't retain the object we still have [SomeOtherClass alloc] as a +1 and this method returns a +1 object. That's already a problem. We now appear to have both the alloc and the init method returning +1. Unfortunately even though we released the old value of self and prevented a leak and everything appears balanced. It is no longer possible to describe the behavior of the init method... =\

Remember that every Objective-C method has two hidden arguments, the receiver and the selector.

 - (instancetype)initWithObject:(Object *)o;

becomes equivalent to C function with the prototype

 id _initWithObject(id self, SEL _cmd, Object *o);

We can document this method for both cases of returning the first argument or a new one.

This method has two possibilities:

  • it returns self

  • it released self and return another object

Luckily the use of retain/release are equivalent and the program does not leak. :D

Ugh yuck! How would you explain that to a compiler?! Oof! Let's look at those two statements again.

  • it returns self

  • it releases self and returns a new object

And emphasize the retain count of the object returned.

  • it returns self

  • it releases self and returns an object that is owned/retained (you must release it!)

It's the same statement but with a reminder. Let's change the first to do a retain and then a release. It's completely silly and wasteful, but run with it for a moment.

  • it returns [[self retain] release];
  • it releases self and returns an object that is owned/retained (you must release it!)

That's silly but it works. But if you look carefully at those two lines they are equivalent. They both release the input argument and returns a retained object that you must later release.

So if this method were just documented semantically it could say the one line:

  • it consumes self and returns a retained object

The word releases was also changed to consume because that is the vocabulary for a function that releases one of its arguments.

Consumption

The init method releases the first argument and returns a retained object. It may be able to optimize away any superfluous retains and releases (by returning self with retaining or releasing it) but that is an implementation detail. ;)

Thus the init method is equivalent to the source line

- (instancetype)initWithObject:(Object *)o
    __attribute__((ns_consumes_self))
    __attribute__((ns_returns_retained));

but the method is known to have those two attributes since it is in the init method family and they're redundant. Those two attributes could also be replaced with the macro NS_REPLACES_RECEIVER.

A semantically equivalent C function would look something like this:

 id _initWithObject(id __attribute__((ns_consumed)) self, SEL _cmd, Object *o)
    __attribute__((ns_returns_retained));

So in summary, alloc returns a retained argument, init returns a retained argument, but init releases the argument too.

So in [[SomeClass alloc] init] you have:

Compiled with MRR (-fno-objc-arc)

 1 id temp = [SomeClass alloc];
 2 id obj = [temp init]; // This releases temp!
 3 ...
 4 [obj release];

And there are no leaks. VoilĂ !

So, the next time someone says to you "which method sets the retain count, alloc or init?" You can respond with "both do!" and then blow a raspberry. :)

Excessive Footnote:

P.S. In reality it is likely that neither retained it. When you retain an object for the first time it is added to a global table. Absence in the table means "retain count 1" and thus *nothing* actually needs to be done to setup up the retain count on creation. The best part of all is that the global table increments and decrements by two, keeping the value always even, so that it can use the the least significant bit of the count as the "is deallocating" flag.