递归块保留周期

Pat*_*ini 20 recursion objective-c objective-c-blocks automatic-ref-counting

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

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

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

myBlock = nil;
Run Code Online (Sandbox Code Playgroud)

rob*_*off 34

您的代码确实包含保留周期,但您可以通过myBlock在递归基本情况(i == 0)中设置为nil来在递归结束时中断保留周期.

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

我使用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;
}
Run Code Online (Sandbox Code Playgroud)

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

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>
Run Code Online (Sandbox Code Playgroud)

我复制了块地址(0x7ff142c24700),将"Console"更改为"Objects List",并将地址粘贴到搜索框中.仪器向我展示了块的分配:

块泄露

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

块泄漏的细节

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

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

块被释放

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

阻止释放细节

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

  • 这样做的问题是,虽然你的块实际上已被释放,但ARC仍然会在此块中强烈"捕获'myBlock'可能会导致保留周期"警告. (4认同)

tc.*_*tc. 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);
Run Code Online (Sandbox Code Playgroud)

您可以添加包装器以获取原始类型签名:

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

myBlockWrapper(10);
Run Code Online (Sandbox Code Playgroud)

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