块捕获对象的过度释放问题;保持计数从+2直接跳到0!

时间:2011-05-14 05:24:27

标签: objective-c instruments grand-central-dispatch retaincount nszombie

我对偶然发生的崩溃感到困惑,根据Zombies乐器,这是由于一些字典值的过度释放造成的。当我查看Instruments中这些过度释放的对象之一的对象历史时,我发现它的保留计数在一个阶段从+2直接下降到0。 (看一下帖子末尾的截图)。我不清楚这是怎么可能的。

我应该说在使用Instruments进行分析时我只会看到这种崩溃,所以我认为它可能是一个Apple漏洞,但是假设它是导航错误可能更安全,而Instruments只是暴露出来。

无论如何,我正在构建一个包含一些Core Foundation对象(CFStrings和CFNumbers)的CFDictionary,然后我将它转换为NSDictionary *并将其传递给Objective-C方法。我的代码的简化版本如下:

// creates a CFDictionary containing some CFStrings and CFNumbers
void doStuff() 
{
    CFDictionaryRef myDict = CreateMyDictionaryContainingCFTypes();

    dispatch_async(myQueue, ^{
        [someObject receiveDictionary:(NSDictionary*)myDict];
        CFRelease(myDict);  // this line causes a crash. The Zombies instrument
                            // claims a CFString value contained in this
                            // dictionary has already been freed.
    });
}

// ...

- (void)receiveDictionary:(NSDictionary*)dict
{
    NSAutoreleasePool *pool = [NSAutoreleasePool new];

    NSString* str1 = [dict objectForKey:@"key1"];
    NSString* str2 = [dict objectForKey:@"key2"];
    NSNumber* num1 = [dict objectForKey:@"key3"];

    dispatch_async(myOtherQueue, ^{
        [database executeUpdate:@"INSERT INTO blah (x,y,z) VALUES (?, ?, ?)", str1, str2, num1];
    });

    [pool drain];
}

我原以为str1str2num1会被视为Objective-C对象,因此会在-receiveDictionary:中的块时捕获并自动保留由dispatch_async调用复制,并在释放该块时释放。实际上,这些变量确实似乎被块捕获并保留。但是,检查Instruments中过度释放的CFString的对象历史记录,我可以看到在复制块时它的引用计数正在递增。令人困惑的是,当一个块被释放时,它的保留计数从+2直接下降到0(参见帖子末尾的截图);我不知道如何从堆栈跟踪告诉它是哪个块。在CFRelease中的块中的字典上调用doStuff()时,其某些值已经被释放,程序崩溃。

那么额外的释放呼叫来自哪里?对象的保留计数如何从+2直接下降到0,如仪器所示?

一时兴起,我强迫第二个块保留整个字典,如下所示:

dispatch_async(myOtherQueue, ^{
    [database executeUpdate:@"INSERT INTO blah (x,y,z) VALUES (?, ?, ?)", str1, str2, num1];
    [dict self];
});

这似乎使崩溃消失了;仪器至少停止报告僵尸。我不能为我的生活看到为什么这样有效;当然我只是为了确保该块保留我感兴趣的字典值,而不是整个字典。那是怎么回事?


Instruments列出了僵尸CFString的以下对象历史记录,其中包含对象的保留计数。我已经为有趣的事件添加了截图。

#0 +1创建CFString
#1 +2 CFString添加到字典中 #2 +1 CFString发布了 #3 +2 CFString is retained when the block in -receiveDictionary: is copied
#4 +0 What the...? The object's retain count dropped straight from +2 to 0!
#5 -1 CFDictionary is released, causing crash

3 个答案:

答案 0 :(得分:0)

CFDictionaryKeyCallBacks创建字典时,您在CFDictionaryValueCallBacksCreateMyDictionaryContainingCFTypes()使用了什么?如果我为两者传入NULL,我可以很容易地复制此问题,但如果我传入&kCFTypeDictionaryKeyCallBacks&kCFTypeDictionaryValueCallBacks,我就无法复制它。

答案 1 :(得分:0)

最后发现了这个错误 - 结果发现它根本不是僵尸问题,而是解码base64数据的例程中无关的内存损坏问题。与保留/释放,阻止或GCD无关。叹息。

事后看来,这应该更加明显。在仪器报告过度释放的物体之后不久,我的程序崩溃的事实应该是一个线索 - 如果它实际上是一个僵尸问题,你就不会发生崩溃。 (我认为?)保留计数从+2跳到0可能表示除了简单的过度发布之外的其他内容。

那我学到了什么?

  • 请勿在未经过彻底检查的情况下复制粘贴代码。所有base64转换例程都不是相同的。 (具体来说,在不使用其返回值的情况下调用realloc是错误的,错误的错误!遗憾的是静态分析器没有标记这一点。)
  • 不要完全依赖仪器 - 其他工具如Valgrind可能很有用。在这种情况下,Valgrind给了我更准确的信息。

答案 2 :(得分:-1)

复制时的块将隐式保留其范围内的任何Objective-C对象,然后在释放块时隐式释放这些对象。

CFDictionaryRefNSDictionary的免费桥接类型,就块而言,还有Objective-C对象。这意味着您不需要进行任何额外的内存管理。

让我评论您的代码,并标记评估顺序。

void doStuff() {
    // 1. myDict must have a retainCount of 1, you named your function Create
    //    and promised so according to Core Foundation men.rules.
    CFDictionaryRef myDict = CreateMyDictionaryContainingCFTypes();

    // 2. dispatch_async will copy your block and retain myDict, since it is in 
    //    scope of the block, myDict has a retainCount of 2
    dispatch_async(myQueue, ^{
        // 4. Block is execute some time later, myDict has a retainCount of 1.
        [someObject receiveDictionary:(NSDictionary*)myDict];

        // 5. Block is done and will be released, along with scoped objects
        //    on exit, retainCount reaches 0, and myDict is released.
    });

    // 3. Release your own copy before function ends, retainCount of 1
    CFRelease(myDict);  // this line causes a crash. The Zombies instrument
}