« Older: Great South Run « Older
Newer: C Pointers »Newer »
Blocks and Memory Management
What happens when you create a block?
Normally when you write a block in Objective C, the compiler actually creates an Objective C object, however the object is likely different to any other Objective C object that you will have seen. One of two types of block will be created:
-
If the block doesn’t use any variables from outside its scope a global block will be created. In concept this works exactly the same as a nameless function; it’s an area of code that you can call into and is always there, it never gets destroyed. If you were to save the address of this block when creating it you could call into this code at any time during the lifetime of your program. As you don’t reference any variables from outside the block, there are no issues with memory management. These blocks are not interesting from a memory management point of view, so will be mostly ignored in the rest of the blog post.
-
If the block references any variables that are declared outside the block then the block will be created as a stack object. This isn’t something that exists in the rest of the Objective C world. The rest of the time when you create an object, memory is put on the heap, not the stack (other languages do support stack objects as a normal concept).
The block being a stack object has some nice advantages. It’s always really fast to create the object; the program’s stack is calculated in advance so there is no need to do any work to find contiguous memory to allocate for the object, it’s already there. The second (and more important) is that all of the object’s memory drops off the stack once you leave the current stack frame, i.e. when you return from your function or method. This means that in a manual reference counted (MRC) environment you can create the block object without having to keep a reference to it to manually free its memory, i.e. you can do this:
We don’t need to worry about the object that this has secretly created because all of that object’s memory will be cleared once we leave the function.
This is very different to other objects on the heap. In an MRC environment the following will cause you to leak memory:
We have created an NSView
but have no way to release it again. To solve this
we would need to do the following:
So by making blocks secretly stack objects, they can work nicely with manual reference counting with a nice syntax where we appear to be just adding a block of code as an argument.
Another important thing to note is that because blocks are secretly
stack objects, they can’t take ownership (i.e. increment the retain count)
of any variables used inside the block that were declared outside the block
(well at least not without the magic of ARC).
The block objects aren’t deallocated; their memory is simply dropped when
the method returns, so under MRC there is nowhere where the block could
remove its ownership of the objects (which would normally be done in
dealloc
). This means that code like this:
may cause a crash as the array object has been deallocated.
(If you’re very unlucky the array
pointer will point at
something that responds to the description
method and you
won’t get the crash.)
As the block is on the stack, keeping a reference to the block that outlasts the scope of the function/method won’t work:
The above code causes a crash (without the reference to self
within the block, a global block is created, so everything would
work fine).
If you want to keep a block around outside of your method
you need to call the copy
method on the block. This method
will copy the block from the stack to the heap. It will also
retain all of the variables that the block references. This
means that the block now owns the variables that are referenced
inside the block. So if we change the block ownership code example
a couple above then we won’t crash:
There are a couple of things to note here. We need to keep
a reference to our copied block; this block is now under the
normal reference counting rules and we need to release it
when appropriate. The variables declared outside the block
that are used within the block have also had their retain
counts incremented, so when we call blockCpy
after releasing
array
we don’t crash.
What changes in ARC?
In ARC the memory management works differently. Suddenly
with ARC there is the magical ability to deallocate the instance
variables of the block when the block drops out of scope.
This means that the block can have strong instance
variables (i.e. it can increment the retainCount
of
its instance variables) without the risk of leaking memory.
When you create a block in ARC the block’s instance variables
(the variables that were declared outside the block,
and used inside the block) are declared with the
same ARC Ownership Qualifiers as they were declared
with outside of the block. This means that if you
use a variable that is declared as __strong
inside
a block, the block will keep a __strong
copy of the
variable (i.e. the variable’s retainCount
will be
incremented). If however the variable’s ownership
qualifier is __weak
or __unsafe_unretained
the
variable’s retainCount
won’t be incremented by the block
(you can’t capture an __autoreleasing
variable in a block).
Here is code similar to the Block Ownership code
from earlier, and it won’t crash:
The block has kept a reference to array
because array
is
declared as __strong
.
Let’s instead try passing in a __weak
reference:
This code will print (null)
to the console. This is
because when the object referenced by a __weak
variable
is deallocated the __weak
variables associated with the
object are set to nil
. In this case when we do array = nil
,
wkArray
is set to nil
.
If instead we gave wkArray
an ownership qualifier of
__unsafe_unretained
we’d likely get a crash
(we’d definitely get a crash if Zombies are enabled).
This is because wkArray
now points to the location
of an object that has been deallocated, and __unsafe_unretained
doesn’t have the magic ability of __weak
to make the
variable nil
when it is deallocated.
In some instances you may be thinking that your code should
always have a reference to the variable that you have
used in a block in a non-__strong
way (i.e. you know that
another __strong
reference to the object exists when you
call the block) and that you’d rather have a crash if there
is no reference to the object. In this case I’d recommend
using __weak
and asserting that the variable exists
within the block, as using __unsafe_unretained
doesn’t
guarantee you will have a crash when you try and reference
the variable.
This makes the ARC situation with blocks very different to the
MRC situation. When you use a __weak
reference within
a block it doesn’t matter if the block is on the stack or the
heap, the block won’t take ownership of the object. Conversely if
you use a __strong
reference regardless of whether the block is
on the heap or the stack the block will take ownership of the object.
The ability to use __weak
references within a block allow you to
avoid retain cycles. The following code will never cause a retain
cycle:
If the block kept a __stong
reference to self
here, we
would have a retain cycle.
However, because the block doesn’t keep a strong reference
to our JTAObject
, if something else has a reference to the
block it’s possible for the block to be called after self
has been deallocated. If this is a possibility in your code,
due to the magic of weak references you can check whether
weakSelf
exists and exit the block early if it doesn’t.
The above applies regardless of whether the block is on the stack or the heap. The memory management is done entirely based on the ownership qualifiers of the objects referenced within the block.
It doesn’t really matter any longer whether a block is on the stack or the heap as ARC will clear things up anyway. It is however interesting to know what types of blocks we are creating:
-
If you create a block and no variables are referenced within the block, then a global block is created and all reference counting (including ARC) is ignored. The following options assume that you reference a variable from outside the block.
-
If you create a block and don’t keep a reference to the block (i.e. you pass it into a method immediately) then a stack block will be created.
-
If you create a block and keep a
__strong
reference to the block (which is the default) the block will be immediately copied to the stack so that ARC can deallocate the block sensibly when it goes out of scope. -
If you create a block and keep an
__unsafe_unretained
reference then the block will be created on the stack like it is in MRC. -
If you try to create a block and only keep a
__weak
reference to it, the block will be moved to the heap and immediately deallocated, probably causing a crash. So this is a very bad idea.
If you’re interested can test the type of block you have by printing the
class block in the debugger (i.e. po [myBlock class]
). Global blocks have
a class __NSGlobalBlock__
, blocks on the heap have a class of
__NSMallocBlock
and blocks on the stack have a class of
__NSStackBlock__
.