Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Block recursion and breaking retain cycle

To better illustrate the question, consider the following simplified form of block recursion:

__block void (^next)(int) = ^(int index) {
    if (index == 3) {
        return;
    }
    int i = index;
    next(++i);
};
next(0);

XCode (ARC-enabled) warns that "Capturing 'next' strongly in this block is likely to lead to a retain cycle".

Agreed.

Question 1: Would the retain cycle be successfully broken by setting the block itself to nil, in this fashion:

__block void (^next)(int) = ^(int index) {
    if (index == 3) {
        next = nil; // break the retain cycle
        return;
    }
    int i = index;
    next(++i);
};
next(0);

(Note: you'd still get the same warning, but perhaps it is unwarranted)

Question 2: What would be a better implementation of block recursion?

Thanks.

like image 856
krisk Avatar asked Feb 12 '13 18:02

krisk


2 Answers

To accomplish the retain-cycle-free recursive block execution, you need to use two block references - one weak and one strong. So for your case, this is what the code could look like:

__block __weak void (^weak_next)(int);
void (^next)(int);
weak_next = next = ^(int index) {
  if (index == 3) {
    return;
  }
  int i = index;
  weak_next(++i);
};
next(0);

Note that the block captures the weak block reference (weak_next), and the external context captures the strong reference (next) to keep the block around. Both references point to the same block.

See https://stackoverflow.com/a/19905407/1956124 for another example of this pattern, which also uses block recursion. In addition, the discussion in the comments section of the following article is relevant here as well: http://ddeville.me/2011/10/recursive-blocks-objc/

like image 137
jmkk Avatar answered Oct 23 '22 08:10

jmkk


I think @newacct is correct about @Matt Wilding's solution; it does seem that nothing will have a strong ref to the next block in that case and will result in a run time exception when run (at least it did for me).

I don't know how common it is to find recursively called blocks in the wild in objc. However, in a real world implementation (if actually required) on say, a view controller, one might define the block and then set up an internal interface property with a strong reference to said block:

typedef void(^PushButtonBlock)();

@interface ViewController ()
@property (strong, nonatomic) PushButtonBlock pushButton;
@end

@implementation ViewController
  ... 
  // (in viewDidLoad or some such)
  __weak ViewController *weakSelf = self;

  self.pushButton = ^() {
    [weakSelf.button pushIt];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), weakSelf.pushButton);
  };

  self.pushButton();
  ...
@end

This runs fine for me and has no compiler warnings about retain cycles (and no leaks in instruments). But, I think I would probably steer clear of doing this (recursive block calls) in most cases in objc - it's smelly. But interesting in any case.

like image 26
boundsj Avatar answered Oct 23 '22 08:10

boundsj