我正在尝试使用 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"];
同样的结果。
我找到了这篇文章,但答案指向另一个问题: NSMutableArray 中的块泄漏(ARC)
然后,这就是魔法:如果我改变这一行:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:@"Key"];
至:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[aBlock copy] forKey:@"Key"];
泄漏消失。
我知道,在非 ARC 下,在传递块文字的引用之前,我必须复制它(当声明文字时,它在堆栈上,所以我需要在传递到函数范围之外之前将它复制到堆已声明)......但使用ARC我不应该关心它。有什么迹象吗?从 5.0 到 6.1 的所有版本都会发生这种情况。
编辑:我做了一些测试,试图了解我是否做错了什么或者是否有一些错误......
第一:我读错了仪器信息吗?我不认为,泄漏是真实的,而不是我的错误。看这张图……执行该方法 20 次后:
第二:如果我尝试在非弧环境中做同样的事情会发生什么?这增加了一些奇怪的行为:
NON-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 文档说,当您将块传递给集合时,您必须使用 [^{} copy]。这是因为 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)