Will this lead to any sort of retain cycle? Is it safe to use?
__block void (^myBlock)(int) = [^void (int i)
{
if (i == 0)
return;
NSLog(@"%d", i);
myBlock(i - 1);
} copy];
myBlock(10);
myBlock = nil;
Will this lead to any sort of retain cycle? Is it safe to use?
__block void (^myBlock)(int) = [^void (int i)
{
if (i == 0)
return;
NSLog(@"%d", i);
myBlock(i - 1);
} copy];
myBlock(10);
myBlock = nil;
No, that will not cause a retain cycle. The
__block
keyword tells the block to not copymyBlock
, which would have occurred before assignment causing the application to crash. If this is not ARC the only thing you will need to do is releasemyBlock
after you callmyBlock(10)
.Here is a modern solution to the problem:
I wanted a solution that gets no warnings, and in this thread https://stackoverflow.com/a/17235341/259521 Tammo Freese gives the best solution:
His explanation makes perfect sense.
If you are using ARC, you have a retain cycle, because
__block
object variables are retained by the block. So the block retains itself. You can avoid it by declaringmyBlock
as both__block
and__weak
.If you are using MRC,
__block
object variables are not retained, and you should have no problem. Just remember to releasemyBlock
at the end.There's a simple solution that avoids the cycle and the potential need to prematurely copy:
You can add a wrapper to get the original type signature back:
This becomes tedious if you want to extend it to do mutual recursion, but I can't think of a good reason to do this in the first place (wouldn't a class be clearer?).
Your code does contain a retain cycle, but you can break the retain cycle at the end of the recursion by setting
myBlock
to nil in the recursion base case (i == 0
).The best way to prove this is to try it, running under the Allocations instrument, with “Discard unrecorded data upon stop” turned off, “Record reference counts” turned on, and “Only track active allocations” turned off.
I created a new Xcode project using the OS X Command-Line Tool template. Here's the entire program:
Then I ran it under the Allocations instrument, with the settings I described above. Then I changed “Statistics” to “Console” in Instruments, to see the program output:
I copied the block address (
0x7ff142c24700
), changed “Console” to “Objects List”, and pasted the address into the search box. Instruments showed me just the allocation for the block:The dot under the Live column means the block was still allocated when the program exited. It was leaked. I clicked the arrow next to the address to see the full history of the block's allocation:
Only one thing ever happened with this allocation: it was allocated.
Next I uncommented the
myBlock = nil
line in theif (i == 0)
statement. Then I ran it under the profiler again. The system randomizes memory addresses for security, so I cleared out the search bar and then checked the Console again for the block's address on this run. It was0x7fc7a1424700
this time. I switched to the “Objects List” view again and pasted in the new address,0x7fc7a1424700
. Here's what I saw:There's no dot under the Live column this time, meaning that the block had been freed by the time the program exited. Then I clicked on the arrow next to the address to see the full history:
This time, the block was allocated, released, and freed.