我对偶然发生的崩溃感到困惑,根据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];
}
我原以为str1
,str2
和num1
会被视为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
答案 0 :(得分:0)
在CFDictionaryKeyCallBacks
创建字典时,您在CFDictionaryValueCallBacks
和CreateMyDictionaryContainingCFTypes()
使用了什么?如果我为两者传入NULL,我可以很容易地复制此问题,但如果我传入&kCFTypeDictionaryKeyCallBacks
和&kCFTypeDictionaryValueCallBacks
,我就无法复制它。
答案 1 :(得分:0)
最后发现了这个错误 - 结果发现它根本不是僵尸问题,而是解码base64数据的例程中无关的内存损坏问题。与保留/释放,阻止或GCD无关。叹息。
事后看来,这应该更加明显。在仪器报告过度释放的物体之后不久,我的程序崩溃的事实应该是一个线索 - 如果它实际上是一个僵尸问题,你就不会发生崩溃。 (我认为?)保留计数从+2跳到0可能表示除了简单的过度发布之外的其他内容。
那我学到了什么?
realloc
是错误的,错误的错误!遗憾的是静态分析器没有标记这一点。)答案 2 :(得分:-1)
复制时的块将隐式保留其范围内的任何Objective-C对象,然后在释放块时隐式释放这些对象。
CFDictionaryRef
是NSDictionary
的免费桥接类型,就块而言,还有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
}