【发布时间】:2013-01-20 04:19:58
【问题描述】:
我正在尝试使用 ARC 了解此代码泄漏的原因:
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:@"Key"];
}
如您所见,我在集合中放置了一个块(NSMutableDictionary,但如果我使用 NSDictionary、NSArray ecc...),然后方法返回并释放字典。然后应该释放块。但是,使用仪器,我看到了泄漏
“只是为了确定”该块没有其他引用,我在方法的末尾添加了这一行:
[dict setObject:[NSNull null] forKey:@"Key"];
同样的结果。
我找到了这篇文章,但答案指向另一个问题: Blocks inside NSMutableArray leaking (ARC)
那么,这就是魔法: 如果我改变这一行:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:@"Key"];
到:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[aBlock copy] forKey:@"Key"];
泄漏消失了。
我知道,在非 ARC 下,在传递块字面量的引用之前,我必须复制它(当声明字面量时,它在堆栈上,所以我需要将它复制到堆中,然后再传递到声明的函数)......但是使用ARC我不应该关心它。 有什么迹象吗? 从 5.0 到 6.1 的所有版本都会发生这种情况。
编辑:我做了一些测试,试图了解我是否做错了什么或者是否有一些错误......
首先:我是否阅读了错误的仪器信息? 我不认为,泄漏是真实的,而不是我的错误。看这张图……执行该方法 20 次后:
第二:如果我尝试在非弧环境中做同样的事情会发生什么? 这增加了一些奇怪的行为:
在非 ARC 环境中的相同功能:
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
[aString release];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[[aBlock copy] autorelease] forKey:@"Key"];
}
在之前的非弧实现中,我只有块的泄漏(不是字符串) 更改实现以在可变字符串声明上使用自动释放解决了泄漏!!!我不明白为什么,我不确定它是否与主要帖子问题有关
// version without leak
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[[NSMutableString alloc] init] autorelease];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[[aBlock copy] autorelease] forKey:@"Key"];
}
结论:
经过各种回答和进一步调查,我明白了一些事情:
1- Apple 文档说,当您将块传递给集合时,您必须使用 [^{} 复制]。这是因为 ARC 不添加副本本身。如果您不这样做,则集合(数组、字典..)会在堆栈分配对象上发送保留 - 它什么也不做。当方法结束时,块超出范围并变为无效。使用它时,您可能会收到错误的访问权限。但请注意:这不是我的情况,我遇到了不同的问题
2- 我遇到的问题不同:该块被过度保留(相反的问题--> 即使不应该存在该块仍然存在)。为什么? 我发现了这一点:在我的示例中,我正在使用此代码
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
此代码在 NON-ARC 下存储对文字块的引用 (aBlock)。该块是在堆栈上分配的,所以如果你 NSLog(@"%p", aBlock) -> 你会看到一个堆栈内存地址
但是,这是“奇怪的”(我在 Apple 文档中没有找到任何指示),如果您在 ARC 和 NSLog aBlock 地址下使用相同的代码,您会看到它现在在 HEAP 上! 由于这个原因,行为是不同的(没有错误的访问)
所以,两者都是不正确但不同的行为:
// this causes a leak
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:@"Key"];
}
// this would cause a bad access trying to retrieve the block from the returned dictionary
- (NSMutableDictionary *)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
return [NSMutableDictionary dictionaryWithObject:^{
NSMutableString __unused *anotherString = aString;
} forKey:@"Key"];
}
3 - 关于我在 NON-ARC 下的最后一次测试,我认为发布的位置错误。在使用 copy-autorelease 将块添加到字典之前,我释放了字符串。 块自动保留块内引用的变量,但保留消息是在复制时发送的,而不是在声明时发送的。所以,如果我在复制块之前释放aString ,它的保留计数变为0,然后块向“僵尸”对象发送保留消息(出现意外行为,它可能会泄漏块,崩溃, ecc ecc)
【问题讨论】:
标签: ios objective-c automatic-ref-counting