【问题标题】:Recursive Blocks in Objective-C leaking in ARCObjective-C 中的递归块在 ARC 中泄漏
【发布时间】:2012-02-10 01:34:54
【问题描述】:

所以我使用递归块。我知道要使块递归,它需要在前面加上 __block 关键字,并且必须将其复制以便可以放在堆上。但是,当我这样做时,它在 Instruments 中显示为泄漏。有人知道我为什么或如何解决它吗?

请注意,在下面的代码中,我引用了许多其他块,但它们都不是递归的。

__block NSDecimalNumber *(^ProcessElementStack)(LinkedList *, NSString *) = [^NSDecimalNumber *(LinkedList *cformula, NSString *function){
        LinkedList *list = [[LinkedList alloc] init];
        NSDictionary *dict;
        FormulaType type;
        while (cformula.count > 0) {
            dict = cformula.pop;
            type = [[dict objectForKey:@"type"] intValue];
            if (type == formulaOperandOpenParen || type == formulaListOperand || type == formulaOpenParen) [list add:ProcessElementStack(cformula, [dict objectForKey:@"name"])];
            else if (type == formulaField || type == formulaConstant) [list add:NumberForDict(dict)];
            else if (type == formulaOperand) [list add:[dict objectForKey:@"name"]];
            else if (type == formulaCloseParen) {
                if (function){
                    if ([function isEqualToString:@"AVG("]) return Average(list);
                    if ([function isEqualToString:@"MIN("]) return Minimum(list);
                    if ([function isEqualToString:@"MAX("]) return Maximum(list);
                    if ([function isEqualToString:@"SQRT("]) return SquareRoot(list);
                    if ([function isEqualToString:@"ABS("]) return EvaluateStack(list).absoluteValue;
                    return EvaluateStack(list);
                } else break;
            }
        }
        return EvaluateStack(list);
    } copy];
    NSDecimalNumber *number = ProcessElementStack([formula copy], nil); 

更新 因此,在我自己的研究中,我发现问题显然与对该块使用的其他块的引用有关。如果我做这样简单的事情,它就不会泄漏:

 __block void (^LeakingBlock)(int) = [^(int i){
        i++;
        if (i < 100) LeakingBlock(i);
    } copy];
    LeakingBlock(1);

但是,如果我在其中添加另一个块,它确实会泄漏:

void (^Log)(int) = ^(int i){
   NSLog(@"log sub %i", i);
};

__block void (^LeakingBlock)(int) = [^(int i){
    Log(i);
    i++;
    if (i < 100) LeakingBlock(i);
} copy];
LeakingBlock(1);

我已经尝试对 Log() 使用 __block 关键字并尝试复制它,但它仍然泄漏。有什么想法吗?

更新 2 我找到了一种防止泄漏的方法,但这有点繁琐。如果我将传入的块转换为弱 id,然后将弱 id 转换回块类型,我可以防止泄漏。

void (^Log)(int) = ^(int i){
    NSLog(@"log sub %i", i);
};

__weak id WeakLogID = Log;

__block void (^LeakingBlock)(int) = [^(int i){
    void (^WeakLog)(int) = WeakLogID;
    WeakLog(i);
    if (i < 100) LeakingBlock(++i);
} copy];
LeakingBlock(1);

肯定有更好的方法吗?

【问题讨论】:

  • 感谢您分享您的研究,我还没有听说过必须同时复制块。但是,似乎更新的 LLVM 在递归调用“在此块中强烈捕获 LeakingBlock”时发出警告可能会导致保留周期。我发现安抚编译器的唯一方法是对块使用一个单独的弱 ptr 有点类似于你下面的答案,尽管它不够实用,我很想在本地覆盖警告。一旦你尝试了最新的编译器,我很想看看你的看法。
  • @smallduck 最初,我使用了copy,因为它会导致将块从堆栈复制到堆中。有一段时间效果很好,我也得到了编译器“递归”错误。我从我的代码中删除了copy(如我的回答中所反映)并且它有效(而以前我会得到EXC_BAD_ACCESS。我猜Apple更改了__block关键字以在堆上而不是在堆上创建块堆栈......但这只是一个猜测。
  • @smallduck 说实话,我已经放弃使用块进行递归了。是的,它可以做到,但它有点笨拙,并且有太多的陷阱。很容易以保留周期结束(这对递归来说真的很糟糕)并且变得难以阅读。所以通常我只是坚持使用方法/函数来进行递归。

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


【解决方案1】:

如果没有更多上下文信息,我可以这样说:

您正在泄漏该块,因为您正在复制它而不是在其他地方发布它。您需要将其复制以将其移动到堆中,没关系。但是你选择的方式并不完全ok。

正确的做法是将它存储为某个对象实例变量,复制它,然后在 dealloc 中释放它。至少,这是一种不泄漏的方法。

【讨论】:

  • 我无法手动释放它。我正在使用 ARC(自动引用计数),它阻止我这样做。块的范围是方法,所以使用 iVar 是代码异味。该块应该在方法结束时被释放。
【解决方案2】:

亚伦,

由于您的代码似乎是单线程的,您为什么要复制该块?如果你不复制块,你就没有泄漏。

安德鲁

【讨论】:

  • 是的,你是对的,代码是单线程的。不过,我不确定我是否理解该声明的意义。如果我不复制该块,当该块尝试递归访问自身时,我会得到 EXC_BAC_ACCESS。
  • 实际上,我应该澄清一下:如果不复制块,我会在分配时获得 EXC_BAD_ACCESS,而不是在块尝试递归访问自身时(如果我不使用 __block 就会发生这种情况)。我有点不确定细节,但我相信这是因为该块最初是作为堆栈上的 const 对象创建的,而在其内部引用的块是完全相同的 const 堆栈块。这是通过首先将块复制到堆中来解决的,然后将其分配给 __block var,这样它就可以引用 if 本身的副本而不是本身的字面量。
【解决方案3】:

好的,我自己找到了答案……但感谢那些试图提供帮助的人。

如果您在递归块中引用/使用其他块,则必须将它们作为弱变量传递。当然,__weak 只适用于块指针类型,所以你必须先对它们进行 typedef。这是最终的解决方案:

    typedef void (^IntBlock)(int);

    IntBlock __weak Log = ^(int i){
        NSLog(@"log sub %i", i);
    };

    __block void (^LeakingBlock)(int) = ^(int i){
        Log(i);
        if (i < 100) LeakingBlock(++i);
    };
    LeakingBlock(1);

上面的代码没有泄露。

【讨论】:

  • 如果你不想要 typedef 就不需要它:void(^__weak log)(int) = ^(int i){...};
  • @MattWilding 不错。我认为这在以前版本的 Xcode 中并不适用。编译器似乎在块方面不断变化。我刚刚下载了 Xcode 4.6,它现在抱怨上面的代码“可能”导致一个保留周期。我认为 Apple 仍在弄清楚整个“块”的事情。
  • 我似乎确实随着每个编译器修订版更改了我的块代码。我在 4.6 中遇到了同样的警告,您也可以通过使用 __weak 修饰符标记 LeakingBlock 来修复它。
猜你喜欢
  • 1970-01-01
  • 2013-05-23
  • 2012-05-17
  • 1970-01-01
  • 2015-06-20
  • 2012-12-09
  • 2023-04-06
  • 2014-09-21
  • 1970-01-01
相关资源
最近更新 更多