我对我看到的偶尔崩溃感到困惑,根据 Zombies 工具,这是由某些字典值的过度释放引起的。当我查看 Instruments 中这些过度释放对象之一的对象历史记录时,我发现它的保留计数在某个阶段直接从 +2 下降到 0。(请看帖子末尾的屏幕截图)。我不清楚这怎么可能。
我应该说我只在使用 Instruments 进行分析时才看到这个崩溃,所以我想这可能是一个 Apple 错误,但假设它是 Pilot 错误可能更安全,而 Instruments 只是暴露了这个错误。
无论如何,我正在构建一个包含一些核心基础对象(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:
在调用复制块 in 时被捕获并自动保留dispatch_async
,并在释放该块时释放。实际上,这些变量似乎确实被块捕获和保留。但是,检查 Instruments 中过度释放的 CFString 的对象历史记录,我可以看到在复制块时它的引用计数正在增加。令人困惑的是,当一个块被释放时,它的保留计数从 +2 直接下降到 0(见帖子末尾的截图);我不知道如何从堆栈跟踪中判断这是哪个块。到时候CFRelease
在块中的字典上调用doStuff()
,它的一些值已经被释放,程序崩溃了。
那么额外的发布调用是从哪里来的呢?正如 Instruments 所指出的,一个对象的保留计数如何从 +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 在-receiveDictionary:
复制
块时保留
#4 +0什么...?对象的保留计数从 +2 直接下降到 0!
#5 -1 CFDictionary 被释放,导致崩溃