我正在尝试使用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次后,请查看此图像:
第二:如果我尝试在非弧形环境中做同样的事情会发生什么? 这增加了一些奇怪的行为:
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不会添加副本本身。如果不这样做,集合(数组,字典..)会在STACK ALLOCATED OBJECT上发送一个retain - 它什么都不做。当方法结束时,块超出范围并变为无效。使用它时,您可能会收到错误的访问权限。但请注意:这不是我的情况,我遇到了不同的问题
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,然后该块向“zombie”对象发送一条保留消息(具有意外行为,它可能泄漏块,崩溃,ecc ecc)
答案 0 :(得分:3)
请参阅此问题以供参考iOS 5 Blocks ARC bridged cast;它展示了Block和ARC的噩梦。
通常,如果将块分配给超出当前范围的变量,编译器将自动将块复制到堆中。这意味着当你超出范围时,你仍然可以使用这个块。同样,块参数也是如此。编译器知道它需要对这些参数进行复制,因此会这样做。
NSArray
等类的问题在于,他们通常不需要复制对象以保持正确;通常他们只保留对象。虽然超出范围的对象是语言的一部分(因此它会复制),但将其保留在像NSArray
这样的对象中是应用程序级操作。因此,编译器还不够聪明,无法确定块需要复制(块毕竟是标准的Obj-C对象,它认为它需要做的就是保留它)。类似的徒劳,这就是为什么持有块的任何属性都需要指定copy
关键字。属性方法的自动合成不知道存储了一个块,并且在设置时需要给它一个微调来复制它们。
这说明了为什么当你在你的块上使用- copy
时,整个事情都有效,你正在做编译器应该做的事情,但是这样做并不聪明...... Apple甚至推荐这种技术其Transitioning to ARC文档,请参阅常见问题解答。
Bootnote:如果您想知道我为什么要保留,即使您正在使用ARC,这就是ARC在幕后所做的事情。内存管理模型仍然和以前一样,但现在系统有责任根据命名和约定为我们管理它,而以前开发人员有责任正确管理内存。只是对于块,系统无法完全管理它,因此开发人员需要不时介入。
答案 1 :(得分:2)
由于性能原因,块在堆栈上开始生命。如果它们的存活时间比堆栈周期长,则必须将它们复制到堆中。
在MRR中,你必须自己复制。如果您将一个块传递给堆栈(即从方法返回),ARC会自动为您执行此操作。但是如果在堆栈中传递一个块(例如,将其存储在NSMutableDictionary
或NSMutableArray
中),则必须自己复制它。
Apple的Transitioning to ARC文档中记录了这一点,在该文档中搜索“块如何在ARC中工作”。
对于非ARC示例(正如您在结论中所写的那样),块的copy
应该在发布aString
之前发生,因为在复制块时保留aString
。否则,您的代码将显示未定义的行为,甚至可能会崩溃。以下是一些演示Non-ARC问题的代码:
NSObject *object = [[NSObject alloc] init];
void (^aBlock)() = ^{
NSLog(@"%@", object);
};
[object release];
aBlock(); // undefined behavior. Crashes on my iPhone.