Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In ARC, why is self in init methods a consumed parameter?

I want to understand ARC, and I'm reading this:

http://clang.llvm.org/docs/AutomaticReferenceCounting.html#consumed-parameters

It says that a consumed parameter is retained before the call, and released at the end of the function (inside the function body). It then says that init... methods are effectively marked with ns_consumes_self. I don't see the point.

Foo *foo = [[Foo alloc] initWithWhatever: x];

So alloc returns an object with a retain count of 1, right? Now it is retained again before going into init, and then released at the end of init, so we're back at 1. Why is it designed like this? When I think about what the typical init looks like, I get more confused.

self = [super init];
if (self) { ... } 
return self;
like image 748
Rob N Avatar asked Jun 21 '13 18:06

Rob N


Video Answer


2 Answers

So alloc returns an object with a retain count of 1, right? Now it is retained again before going into init, and then released at the end of init, so we're back at 1. Why is it designed like this?

Probably because initializers are free to return a pointer to a different object than the one that self points to initially. An -init method can say: I don't like this object, I think I'll substitute a different one and that's perfectly OK. Before ARC, such an initializer would explicitly release self (even though it never retained it) and then assign some other pointer to it. Presumably, the ns_consumes_self directive will take care of releasing the object that was passed into the initializer even if self is changed to point to some other object inside the method.

When I think about what the typical init looks like, I get more confused.

It's a good bet that the behavior is there to cover cases that don't look like a typical -init method. Modifying self isn't exactly typical, it's just permissible.

like image 82
Caleb Avatar answered Oct 17 '22 05:10

Caleb


Why shouldn't it be that way? Calls to -init are meant to return a given object with a +1 retain count, and doing a "temporary retain" is the safest way of guaranteeing that self remains alive throughout the entirety of a given init method. Consider what happens if we peel back the layer of Objective-C abstraction and turn -init into the function pointer it's IMP resolves to:

id init(id self, SEL _cmd) {
    //+0 self given requires explicit retain to guarantee lifetime
    //...
    //(if retained) +1 self is returned; (if not retained) +0 self is deallocated
}

If given a self with a +0 retain count (as is common in most methods that retain their arguments i.e. setters), then you would have to guarantee that somewhere up the inheritance chain someone was nice enough to retain self away from whatever happened to allocate it (ending in self having a rather ambiguous retain count of +1). But, if you receive a self with a +1 retain count, and you do a retain-release it yourself,you can be certain that it remains alive through your -init method, and that you and you alone have ownership of self. And if the given self was not "alive", then you're guaranteed to return nil rather than an object in the middle of deallocation. Thus, the above becomes (in pseudo-C):

id init(__attribute((ns_consumed))id self, SEL _cmd) {
    //implicit retain of +1 self returns self with +2
    //...
    //implicit release of +2 self returns self with +1
}

I like to call this pattern "old-school atomic access", and it's used in Apple frameworks designed before @synchronized {} atomic getters were invented. When you use a getter backed with a public iVar. You'll often see methods that follow that pattern written like:

- (NSView *)view {
    //explicit retain-autorelease of +1 variable is +2 -> +1, guaranteed access or nil.
    return [[_view retain]autorelease];
}

But all of this doesn't touch the ownership rules of the exception that is -init and family. -init methods return objects +1, but who exactly owns them? Well, the allocator* still has a hand in the reference to the variable, and self = [super init] doesn't actually retain anything (and it also has to obey the whole "returns +1" rule). Well, again I have to turn to pseudo-code, but this time it'll be in Objective-C:

- (id)init {
    //self is +1 because -init must return +1
    self = [super init];
    //implicit [self retain]; leaves self with a +2 reference count
    //...
    //implicit [self autorelease]; leaves self with a +1 reference count
    return self;
}

OK, so now you've got a +1 object floating around claimed by the allocator, so how do you "claim" it? Assignment, of course! The point of implicitly __strong locals and __strong properties is to reclaim the object from the allocator entity.

- (void)f {
    //Freestanding +1 variable is not owned by the caller, will be deallocated when the method
    //passes out of scope.
    [[NSObject alloc]init];

    //Implicitly __strong local captures reference away from allocator entity, 
    //which "autoreleases" it's ownership
    //We now "own" the returned object.
    NSObject *obj = [[NSObject alloc]init];

    //Strong property captures reference away from local, saves the object from being deallocated
    //when the method passes out of scope
    self.object = obj;
}

*Allocator, in the context of this answer, refers to the functions that eventually call malloc() to allocate space for the object.

like image 31
CodaFi Avatar answered Oct 17 '22 05:10

CodaFi