20

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;
Patrick Perini
  • 22,555
  • 12
  • 59
  • 88
  • 1
    I've posted a simple way to solve retain cycles in recursive blocks. Which also works under ARC: http://stackoverflow.com/a/14730061/439096 – Berik Feb 06 '13 at 13:23

6 Answers6

34

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:

#import <Foundation/Foundation.h>

void test() {
    __block void (^myBlock)(int) = [^void (int i){
        if (i == 0) {
//            myBlock = nil;
            return;
        }
        NSLog(@"myBlock=%p %d", myBlock, i);
        myBlock(i - 1);
    } copy];
    myBlock(10);
}

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        test();
    }
    sleep(1);
    return 0;
}

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:

2012-10-26 12:04:31.391 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 10
2012-10-26 12:04:31.395 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 9
2012-10-26 12:04:31.396 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 8
2012-10-26 12:04:31.397 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 7
2012-10-26 12:04:31.397 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 6
2012-10-26 12:04:31.398 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 5
2012-10-26 12:04:31.398 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 4
2012-10-26 12:04:31.399 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 3
2012-10-26 12:04:31.400 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 2
2012-10-26 12:04:31.401 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 1
<End of Run>

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:

block leaked

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:

block leaked detail

Only one thing ever happened with this allocation: it was allocated.

Next I uncommented the myBlock = nil line in the if (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 was 0x7fc7a1424700 this time. I switched to the “Objects List” view again and pasted in the new address, 0x7fc7a1424700. Here's what I saw:

block freed

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:

block freed detail

This time, the block was allocated, released, and freed.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • 1
    In this case why is the block strongly captured even if it has a *__block* storage specifier? – Ramy Al Zuhouri Jul 02 '13 at 11:25
  • @RamyAlZuhouri Read the clang ARC documentation. [Section 7.5](http://clang.llvm.org/docs/AutomaticReferenceCounting.html#blocks) says “The inference rules apply equally to __block variables, which is a shift in semantics from non-ARC, where __block variables did not implicitly retain during capture.” “Inference” is a link to [§4.4](http://clang.llvm.org/docs/AutomaticReferenceCounting.html#ownership-inference), which says “If an object is declared with retainable object owner type, but without an explicit ownership qualifier, its type is implicitly adjusted to have __strong qualification.” – rob mayoff Jul 02 '13 at 21:23
  • @RamyAlZuhouri Therefore, under ARC, a `__block` variable without an explicit ownership qualifier is qualified `__strong` and retains the object it references. – rob mayoff Jul 02 '13 at 21:24
  • 4
    The problem with this is, that although your block is in fact freed, ARC will still throw the "Capturing 'myBlock' strongly in this block is likely to lead to a retain cycle" warning. – SG1 Sep 21 '13 at 21:49
  • Yep, I saw the same warning when I tried this. I ended up using tc.'s solution below. Seemed to work well. – stuckj Nov 26 '13 at 22:01
14

There's a simple solution that avoids the cycle and the potential need to prematurely copy:

void (^myBlock)(id,int) = ^(id thisblock, int i) {
    if (i == 0)
      return;

    NSLog(@"%d", i);
    void(^block)(id,int) = thisblock;
    block(thisblock, i - 1);
  };

myBlock(myBlock, 10);

You can add a wrapper to get the original type signature back:

void (^myBlockWrapper)(int) = ^(int i){ return myBlock(myBlock,i); }

myBlockWrapper(10);

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?).

tc.
  • 33,468
  • 5
  • 78
  • 96
2

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 declaring myBlock 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 release myBlock at the end.

newacct
  • 119,665
  • 29
  • 163
  • 224
1

No, that will not cause a retain cycle. The __block keyword tells the block to not copy myBlock, 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 release myBlock after you call myBlock(10).

Joe
  • 56,979
  • 9
  • 128
  • 135
  • More details can be found here: http://www.friday.com/bbum/2009/08/29/blocks-tips-tricks/ – Joe Oct 26 '12 at 16:44
  • So, should I release it after my first call, or after the last call? – Patrick Perini Oct 26 '12 at 16:47
  • Once you are done with it. Do not release it inside the block, release after the call to `myBlock(10)`. I added that to the answer. – Joe Oct 26 '12 at 16:49
  • If this is ARC, then there is no need to delete `myBlock`, and trying to do so with `Block_release` will cause a EXEC_BAD_ACCESS when ARC tries to release it, or worse, release the wrong object. – Richard J. Ross III Oct 26 '12 at 16:51
  • @RichardJ.RossIII Check my updates? I was just going to set `myBlock` to `nil`. – Patrick Perini Oct 26 '12 at 16:54
  • 2
    @pcperini that's not necessary in the slightest. ARC does this automatically. – Richard J. Ross III Oct 26 '12 at 16:54
  • 1
    Joe, this answer is unfortunately wrong entirely for ARC code, which is what the OP appears to be targeting. For more information, read the answer here: http://stackoverflow.com/questions/10274511/block-variable-results-in-nil-value-when-go-out-of-block?rq=1. `__block` with ARC **does** copy it's contents, unlike what you said, and you don't need to `release` the block under ARC. – Richard J. Ross III Oct 26 '12 at 16:55
  • @Joe If you delete that last sentence, I'll accept :) Thanks guys! – Patrick Perini Oct 26 '12 at 17:02
  • Sorry, I forgot to say **NOT** ARC. I changed that. – Joe Oct 26 '12 at 17:23
  • @RichardJ.RossIII Ya that was definitely a typo. – Joe Oct 26 '12 at 17:24
  • @RichardJ.RossIII But *__block* has been made to avoid retain cycles, then why does it copy the variable? This is like a non-block variable works inside a block. – Ramy Al Zuhouri Jul 02 '13 at 11:31
  • 1
    @RamyAlZuhouri in this sense, 'copy' means to capture a variable by value (which is what blocks do by default)). __block makes it capture the variable by reference. – Richard J. Ross III Jul 02 '13 at 13:49
  • @RichardJ.RossIII So the block variable referenced inside the block, has still an increased retain count, but it's the same variable as the one outside the block. Is that right? – Ramy Al Zuhouri Jul 02 '13 at 15:30
1

I wanted a solution that gets no warnings, and in this thread https://stackoverflow.com/a/17235341/259521 Tammo Freese gives the best solution:

__block void (__weak ^blockSelf)(void);
void (^block)(void) = [^{
        // Use blockSelf here
} copy];
blockSelf = block;
    // Use block here

His explanation makes perfect sense.

Community
  • 1
  • 1
malhal
  • 26,330
  • 7
  • 115
  • 133
0

Here is a modern solution to the problem:

void (^myBlock)();
__block __weak typeof(myBlock) weakMyBlock;
weakMyBlock = myBlock = ^void(int i) {
    void (^strongMyBlock)() = weakMyBlock; // prevents the block being delloced after this line. If we were only using it on the first line then we could just use the weakMyBlock.
    if (i == 0)
        return;

    NSLog(@"%d", i);
    strongMyBlock(i - 1);
};
myBlock(10);
malhal
  • 26,330
  • 7
  • 115
  • 133