【问题标题】:Recursive Block Retain Cycles递归块保留周期
【发布时间】:2012-10-26 16:13:34
【问题描述】:

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

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

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

myBlock = nil;

【问题讨论】:

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


【解决方案1】:

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

证明这一点的最佳方法是尝试在 Allocations 工具下运行,关闭“Discard unrecorded data on stop”,打开“Record reference counts”,关闭“Only track active allocations”。

我使用 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;
}

然后我在 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>

我复制了块地址(0x7ff142c24700),将“控制台”更改为“对象列表”,并将地址粘贴到搜索框中。 Instruments 向我展示了该块的分配情况:

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

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

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

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

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

【讨论】:

  • 在这种情况下,为什么即使块有 __block 存储说明符,它也会被强捕获?
  • @RamyAlZuhouri 阅读 clang ARC 文档。 Section 7.5 说“推理规则同样适用于 __block 变量,这是从非 ARC 的语义转变,其中 __block 变量在捕获期间没有隐式保留。” “推理”是指向§4.4 的链接,它表示“如果一个对象声明为具有可保留对象所有者类型,但没有明确的所有权限定符,则其类型被隐式调整为具有 __strong 限定符。”
  • @RamyAlZuhouri 因此,在 ARC 下,没有明确所有权限定符的 __block 变量被限定为 __strong 并保留它引用的对象。
  • 这样的问题是,虽然你的块实际上被释放了,但 ARC 仍然会抛出“在这个块中强烈捕获 'myBlock' 可能会导致保留周期”警告。跨度>
  • 是的,我在尝试此操作时看到了相同的警告。我最终在下面使用了 tc. 的解决方案。似乎运作良好。
【解决方案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);

如果您想扩展它以进行相互递归,这将变得乏味,但我想不出一个好的理由首先这样做(类不会更清晰吗?)。

【讨论】:

  • 很棒的解决方案,非常适合多次重复动画
【解决方案3】:

如果您使用 ARC,则有一个保留循环,因为 __block 对象变量被块保留。所以块保留自己。你可以通过将myBlock 声明为__block__weak 来避免它。

如果你使用的是 MRC,__block 对象变量不会被保留,你应该没有问题。请记住在最后释放myBlock

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 更多细节可以在这里找到:friday.com/bbum/2009/08/29/blocks-tips-tricks
    • 那么,我应该在第一次通话后释放它,还是在最后一次通话后释放它?
    • @pcperini 这一点都没有必要。 ARC 会自动执行此操作。
    • 乔,不幸的是,这个答案对于 ARC 代码完全是错误的,而这正是 OP 的目标。有关更多信息,请在此处阅读答案:stackoverflow.com/questions/10274511/…__block 与 ARC确实复制它的内容,不像你说的那样,你不需要 release ARC 下的块。
    • @RamyAlZuhouri 在这个意义上,“复制”意味着按值捕获变量(这是块默认执行的操作)。 __block 使其通过引用捕获变量。
    【解决方案5】:

    我想要一个没有警告的解决方案,在这个帖子中 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
    

    他的解释很有道理。

    【讨论】:

      【解决方案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);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-12-07
        • 1970-01-01
        • 1970-01-01
        • 2013-09-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多