I am working through some retain-cycle issues with blocks/ARC, and I am trying to get my head around the nuances. Any guidance is appreciated.
Apple\'s documentation
Think of the case of using instanceVariable
as an equivalent of writing self->instanceVariable
. An instance variable is by definition "attached" to the self
object and exists while the self object exists.
Using instanceVariable
(or self->instanceVariable
) means that you start from the address of self, and ask for an instance variable (that is offset of some bytes from the self
object original address).
Using localVariable
is a variable on its own, that does not rely on self and is not an address that is relative to another object.
As the blocks capture the variables when they are created, you typically prefer using instance variables when you mean "when the block is executed, I want to get the value of the instance variable at the time of the execution" as you will ask the self
object for the value of the instance variable at that time (exactly the same way as you would call [self someIVarAccessorMethod]
). But then be careful not to create some retain cycles.
On the other hand, if you use localVariable
, the local variable (and not self
) will be captured when the block is created, so even if the local variable changes after the block creation, the old value will be used inside the block.
// Imagine instanceVariable being an ivar of type NSString
// And property being a @property of type NSString too
instanceVariable = @"ivar-before";
self.property = @"prop-before";
NSString* localVariable = @"locvar-before";
// When creating the block, self will be retained both because the block uses instanceVariable and self.property
// And localVariable will be retained too as it is used directly
dispatch_block_t block = ^{
NSLog(@"instance variable = %@", instanceVariable);
NSLog(@"property = %@", self.property);
NSLog(@"local variable = %@", localVariable);
};
// Modify some values after the block creation but before execution
instanceVariable = @"ivar-after";
self.property = @"prop-after";
localVariable = @"locvar-after";
// Execute the block
block();
In that example the output will show that instanceVariable
and self.property
are accessed thru the self
object, so self was retained but the value of instanceVariable
and self.property
are queried in the code of the block and they will return their value at the time of execution, respectively "ivar-after"
and "prop-after"
. On the other hand, localVariable
was retained at the time the block was created and its value was const-copied at that time, so the last NSLog
will show "locvar-before"
.
self
is retained when you use instance variables or properties or call methods on self itself in the code of the block. Local variables are retained when you use them directly in the code of the block.
Note: I suggest you watch the WWDC'11 and WWDC'12 videos that talks about the subject, they are really instructive.
Is this appropriate use of the "by value" / "by reference" terminology? It's at least analogous to the typical use. Copying a value to a local variable is like copying a value onto the stack; using an ivar is like passing a pointer to a value that's stored somewhere else.
I do not see how self is being referenced in the "by reference" example? When you use an instance variable inside a method, the reference to self
is implied. Some people actually write self->foo
instead of foo
to access an ivar just to remind themselves that foo
is an ivar. I don't recommend that, but the point is that foo
and self->foo
mean the same thing. If you access an ivar inside a block, self
will be retained in order to ensure that the ivar is preserved for the duration of the block.
Do you have any guidance on when one might want to do things one way or the other? It's useful to think in terms of the pass by reference/pass by value distinction here. As AliSoftware explained local variables are preserved when the block is created, just as parameters passed by value are copied when a function is called. An ivar is accessed through self just as a parameter passed by reference is accessed through a pointer, so its value isn't determined until you actually use it.
it seems like this is going to result in a lot of additional code for additional variable declarations? Blocks have been a feature of the language for a while now, and I haven't noticed that this is a problem. More often, you want the opposite behavior: a variable declared locally that you can modify within a block (or several blocks). The __block
storage type makes that possible.
it seems like it may be more maintainable to avoid declaring the blocks inside of each other as one could ultimately end up with a potpourri of unintentionally retained objects? There's nothing wrong with letting one or more blocks retain an object for as long as they need it -- the object will be released just as soon as the blocks that use it terminate. This fits perfectly with the usual Objective-c manual memory management philosophy, where every object worries only about balancing its own retains. A better reason to avoid several layers of nested blocks is that that sort of code may be more difficult to understand than it needs to be.