递归块保留周期

时间:2012-10-26 16:13:34

标签: objective-c recursion automatic-ref-counting objective-c-blocks

这会导致任何类型的保留周期吗?使用安全吗?

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

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

myBlock = nil;

6 个答案:

答案 0 :(得分:34)

您的代码确实包含保留周期,但您可以通过在递归基本情况下将myBlock设置为nil来在递归结束时中断保留周期({{1} })。

证明这一点的最佳方法是尝试在分配工具下运行,“停止时丢弃未记录的数据”关闭,“记录参考计数”打开,“仅跟踪活动分配”关闭。 / p>

我使用OS X命令行工具模板创建了一个新的Xcode项目。这是整个计划:

i == 0

然后我使用上面描述的设置在Allocations仪器下运行它。然后我在Instruments中将“Statistics”更改为“Console”,以查看程序输出:

#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> ),将“控制台”更改为“对象列表”,并将地址粘贴到搜索框中。仪器向我展示了块的分配:

block leaked

Live列下的点表示程序退出时仍然分配了块。它被泄露了。我单击地址旁边的箭头以查看块分配的完整历史记录:

block leaked detail

这个分配只发生过一件事:它被分配了。

接下来,我在0x7ff142c24700语句中取消注释了myBlock = nil行。然后我再次在探查器下运行它。系统随机化内存地址以确保安全性,因此我清除了搜索栏,然后再次检查控制台以查看此运行中的块地址。这次是if (i == 0)。我再次切换到“对象列表”视图并粘贴到新地址0x7fc7a1424700中。这就是我所看到的:

block freed

这次Live列下没有任何点,这意味着程序退出时块已被释放。然后我点击地址旁边的箭头查看完整的历史记录:

block freed detail

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

答案 1 :(得分:14)

有一个简单的解决方案可以避免周期和潜在的过早复制需求:

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

如果你想扩展它以进行相互递归,这就变得乏味了,但是我不能想到一个很好的理由这样做(不会让一个类更清楚吗?)。

答案 2 :(得分:2)

如果您使用ARC,则会有一个保留周期,因为块保留了__block个对象变量。所以该块保留了自己。您可以通过将myBlock声明为__block__weak来避免这种情况。

如果您使用的是MRC,则不会保留__block个对象变量,您应该没有问题。请记住最后发布myBlock

答案 3 :(得分:1)

不,这不会导致保留周期。 __block关键字告诉块不要复制myBlock,这会在分配导致应用程序崩溃之前发生。如果这不是ARC,那么您需要做的就是在致电myBlock后发布myBlock(10)

答案 4 :(得分:1)

我想要一个没有警告的解决方案,在这个帖子中https://stackoverflow.com/a/17235341/259521 Tammo Freese提供了最佳解决方案:

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

他的解释很有道理。

答案 5 :(得分:0)

以下是解决问题的现代解决方案:

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