17

为了更好地说明这个问题,请考虑以下块递归的简化形式:

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

XCode(启用 ARC)警告“在此块中强烈捕获 'next' 可能会导致保留周期”。

同意。

问题 1:是否可以通过将块本身设置为 来成功打破保留周期nil,以这种方式:

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

(注意:您仍然会收到相同的警告,但也许这是没有根据的)

问题 2:块递归的更好实现是什么?

谢谢。

4

2 回答 2

5

要完成无保留循环的递归块执行,您需要使用两个块引用 - 一个弱引用和一个强引用。因此,对于您的情况,代码可能如下所示:

__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);

请注意,块捕获弱块引用(weak_next),外部上下文捕获强引用(next)以保持块周围。两个引用都指向同一个块。

有关此模式的另一个示例,请参见https://stackoverflow.com/a/19905407/1956124,该示例也使用块递归。此外,以下文章评论部分的讨论也与此相关:http: //ddeville.me/2011/10/recursive-blocks-objc/

于 2014-10-30T16:35:37.887 回答
1

我认为@newacct 关于@Matt Wilding 的解决方案是正确的;在这种情况下,似乎没有什么对下一个块有强引用,并且在运行时会导致运行时异常(至少对我来说是这样)。

我不知道在 objc 中在野外找到递归调用的块有多普遍。然而,在现实世界的实现(如果实际需要)中,比如说视图控制器,可能会定义块,然后设置一个内部接口属性,并对该块具有强引用:

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

这对我来说运行良好,并且没有关于保留周期的编译器警告(并且仪器中没有泄漏)。但是,我认为在 objc 的大多数情况下,我可能会避免这样做(递归块调用)——它很臭。但无论如何都很有趣。

于 2013-02-13T07:58:33.427 回答