为了更好地说明问题,请考虑以下简化形式的块递归:
__block void (^next)(int) = ^(int index) {
if (index == 3) {
return;
}
int i = index;
next(++i);
};
next(0);
XCode(启用ARC)警告“在此块中强烈捕获'下一个'可能会导致保留周期”。
同意。
问题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 :什么是更好的块递归实现?
感谢。
答案 0 :(得分:4)
要完成无保留周期的递归块执行,您需要使用两个块引用 - 一个弱引用和一个强引用。因此,对于您的情况,这就是代码的样子:
__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/
答案 1 :(得分: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的大多数情况下,我可能会避免这样做(递归块调用) - 它很臭。但无论如何都很有趣。