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;
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With