递归块保留周期(Recursive Block Retain Cycles)

2019-07-02 17:02发布

这是否会导致任何形式的保留周期? 是否可以安全使用?

__block void (^myBlock)(int) = [^void (int i)
{
    if (i == 0)
        return;

    NSLog(@"%d", i);
    myBlock(i - 1);
} copy];
myBlock(10);

myBlock = nil;

Answer 1:

您的代码包含保留周期,但你可以打破在递归的终点通过设置保留周期myBlock在递归基本情况为零( i == 0 )。

为了证明这一点的最好办法是尝试吧,分配仪器下运行,用“放弃时停止未记录数据”关闭“记录的引用计数”开启,“只跟踪活动分配”关闭。

我创建使用OS X命令行工具模板创建一个新的Xcode项目。 这里就是整个程序:

#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;
}

然后,我跑到它分配仪器下,与我上述的设置。 然后,我在仪器改“统计”到“控制台”,看看该程序的输出:

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>

我复制了块地址( 0x7ff142c24700 ),改“控制台”到“对象列表”,并粘贴地址到搜索框中。 仪器显示,我只是块分配:

实时栏下点意味着这个程序退出时该块还在分配。 它被泄露。 我点击旁边看到块分配的完整历史记录的地址的箭头:

只有一件事都发生在本配置:它被分配。

接着我未注释的myBlock = nil的线if (i == 0)语句。 然后,我又跑了它的探查下。 系统会随机内存地址的安全性,所以我清空了搜索栏,然后再检查控制台块的这个运行地址。 这是0x7fc7a1424700这个时候。 我又贴切换到“对象列表”视图中的新地址, 0x7fc7a1424700 。 这是我看到:

有实时栏下没有点这个时间,这意味着该块已被程序退出时释放。 然后我点击旁边看到完整的历史地址的箭头:

这一次,块被分配,释放和解脱。



Answer 2:

有避免循环,并过早地复制的潜在需要一个简单的解决方案:

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

您可以添加一个包装来获得原始类型的签名后面:

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

myBlockWrapper(10);

如果你想扩展它做相互递归这变得乏味,但我想不出一个很好的理由摆在首位做到这一点(不会一类更清楚?)。



Answer 3:

如果你正在使用ARC,你有保有周期,是因为__block对象变量由块保留。 所以,该块保留本身。 您可以通过声明避免myBlock既是__block__weak

如果您正在使用MRC, __block对象变量没有保留下来,您应该没有问题。 不过,别忘了释放myBlock结尾。



Answer 4:

不,这不会引起保留周期。 该__block关键字告诉块不可复制myBlock ,这将分配之前导致应用程序崩溃时有发生。 如果这不是圆弧,你需要做的唯一事情是释放myBlock你打电话后myBlock(10)



Answer 5:

我想一个解决方案,就没有得到警告,并在此线程https://stackoverflow.com/a/17235341/259521 Tammo福瑞斯提供了最好的解决方案:

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

他的解释非常有意义。



Answer 6:

这里是一个现代的解决问题的办法:

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


文章来源: Recursive Block Retain Cycles