我正在使用cocos2d制作iPhone应用程序,我在使用泄漏工具标记了一个NSMutableArray时遇到了一些麻烦,我的应用程序显然已经泄漏了。我已经解决了这个问题,但我真的不明白它为什么会出现在这里,所以希望有人可以向我解释。
我已将CCParticleSystemQuad子类化,以便我可以添加一些实例变量,包括一个名为'damagedObjects'的NSMutable数组:
@interface SonicBoom : CCParticleSystemQuad{
NSMutableArray *damagedObjects;
HelloWorldLayer *gameClass;
CGPoint radiusPoint;
CGPoint origin;
}
@property(nonatomic, retain) NSMutableArray *damagedObjects;
@property(nonatomic, retain) HelloWorldLayer *gameClass;
@property CGPoint radiusPoint;
@property CGPoint origin;
@end
这被初始化并且在主游戏类中分配了damagedObjects数组,通过设置autoRemoveOnFinish属性,粒子系统在完成时被删除:
-(void)createSonicBoom{
sonicBoom = [SonicBoom particleWithFile:@"SonicBoom.plist"];
sonicBoom.damagedObjects = [[NSMutableArray alloc]init];
sonicBoom.gameClass = self;
sonicBoom.autoRemoveOnFinish = YES;
//etc..........
然后我重写了'SonicBoom'类的dealloc方法,以释放'damagedObjects'数组:
-(void)dealloc{
NSLog(@"Removing SonicBoom");
NSLog(@"damaged objects retain count = %i", damagedObjects.retainCount);
gameClass.sonicBoomActive = NO;
[damagedObjects removeAllObjects];
[damagedObjects release];
[damagedObjects release];
[super dealloc];
}
由于某种原因,只有一个发布消息到阵列我得到了泄漏。我检查了保留计数(我从不打扰这些通常),它是2,所以我现在发送两次发布消息,这似乎已经解决了这个问题。
这是发布它的最好方法,有人可以解释为什么要这样做吗?
由于
答案 0 :(得分:5)
这是因为这条线:
sonicBoom.damagedObjects = [[NSMutableArray alloc]init];
init将引用计数增加1,然后设置retain属性也会增加它。
将其更改为:
NSMutableArray *array = [[NSMutableArray alloc]init];
sonicBoom.damagedObjects = array;
[array release];
或者你可以使用:
sonicBoom.damagedObjects = [NSMutableArray array];
返回一个自动释放的对象,你的类拥有的唯一对象是它与setter保留的对象。
此外,FWIW通过在dealloc中释放两次来修复泄漏绝对不是一个好主意。如果有一天你决定使用其他一些返回自动释放阵列的方法来设置damagedObjects
,那么你的应用就会开始崩溃并追踪崩溃,这样可能很痛苦。
答案 1 :(得分:3)
这一行:
sonicBoom.damagedObjects = [[NSMutableArray alloc]init];
是问题所在。粗略地说,这是编译器扩展到的内容:
[sonicBoom setDamagedObjects:[[[NSMutableArray alloc]init]retain]];
sonicBoom,让它的damagedObjects
属性使用保留限定符,尝试将声明放入您在方法中创建的数组,增加它的保留计数在1和已经由alloc和init对固有返回的1之上。 (alloc最终调用
因此,您有两个对数组的引用,因为您不遵循标准的可可内存管理指南或MVC。自动释放数组或使用方便构造函数[NSMutableArray array];
(为您自动释放,因为cocoa只允许严格的方法子集返回对对象的+1引用)。更好的是,让音爆对象创建自己的阵列,以便它拥有"拥有"在记忆方面。
编辑(对于那些认为我提供的详细程度不足的人< cough >)。
retain
作为Objective-C对象的手动引用计数环境中的内存限定符(特别是那些来自NSObject或NSProxy的对象),使用标准的编译器生成的setter通过使用@synthesize指令,由一组标准调用组成,这些调用可能会或可能不会以下列形式出现(生成的setter中保留计数如何平衡的一般概念几乎与下面的伪代码相同):
- (void)setMyObject:(NSObject*)newMyObject {
[_myObject release];
_myObject = [newMyObject retain];
}
当然,这是设置者的nonatomic
(能够被另一个线程打断)变体。 atomic
版本的实现与
- (void)setMyObject:(NSObject*)newMyObject {
@synchronized(self) {
[_myObject release];
_myObject = [newMyObject retain];
}
}
显然,这些省略了Cocoa setter中固有的Key-Value-Coding机制,但这些运算符一般不会被Apple公开,并且不受内存管理等主题的约束,所以它的实现就剩下了作为读者的练习。
然而,我们可以通过评估Open Source version of NSObject发出的调用*来仔细研究内存管理。
*请注意,此版本的NSObject对应于Mac OS X SDK中的版本,因此,并非所有NSObject的底层实现都能保证与提供的版本相匹配。
-retain
,在撰写本文时,由NSObject(运行时版本532,版本2)的最新开源版本实现,一个接一个地调用3个单独的内部构造,应该前一个失败,最终结束于"缓慢保留"根NSObject的。需要注意的是:NSObject是在Objective-C ++中实现的,对内部LLVM库进行无偿调用。这就是我们对NSObject的分析如果遇到就会结束的地方。
-retain
作为一个16字节对齐的方法实现,该方法在成功时返回对象本身。根据NSObject.mm,保留看起来像这样:
- (id)retain __attribute__((aligned(16))) {
if (OBJC_IS_TAGGED_PTR(self)) return self;
SideTable *table = SideTable::tableForPointer(self);
if (OSSpinLockTry(&table->slock)) {
table->refcnts[DISGUISE(self)] += 2;
OSSpinLockUnlock(&table->slock);
return self;
}
return _objc_rootRetain_slow(self);
}
当在启动时传递适当的标志以便于调试时,* retain将替换为ObjectAlloc。目前尚未提供对ObjectAlloc的分析。
要检查每个部分,还需要访问文件objc-private.h
,该文件也可以自由地与同一目录中此帖子中标记的NSObject版本一起使用。
首先,保留检查指针是否已被标记。当然,标签可能意味着什么,但是对于NSObject,它意味着指针是否包含其最后一位的0x1地址(如果您还记得,由于16字节对齐,除了char *之外的所有类型都具有由Mac OS X保证在其地址末尾有0。因此,OBJC_IS_TAGGED_PTR(PTR)
扩展为
((uintptr_t)(PTR) & 0x1)
如果指针被标记,NSObject会轻松地退出并简单地返回自己(因为通常标记的指针指示无效的地址)。
接下来,-retain
在表上尝试自动锁定(注意OS
- iOS上不可用的前缀方法)。从NSObject的意义上讲,表是跟踪对象的保留计数的东西。它们是与根NSObject一起分配的非常简单的C ++类。一个有趣的技巧在于DISCGUISE(x)
宏。它扩展到:
#define DISGUISE(x) ((id)~(uintptr_t)(x))
如果您注意到,它正在翻转给定对象的指针。我只能假设这样做是为了隐藏SideTable
来自工具的下一行上对象引用计数的双倍增量,因为任何具有来自一次调用的保留计数加倍的对象都可能被视为未定义行为(当-release
被发送时,SideTable被告知将保留计数减少2)。引用计数增加2,以便保持可用地址的最低位以检查对象是否处于被解除分配的过程中(再次绕回标记指针)。如果一切顺利,则释放自旋锁,并且NSObject返回自身。
如果旋转锁定恰好不能用于拍摄,NSObject会调整到它所谓的“慢速保留”(因为锁定和解锁SideTable
的旋转锁的过程相当昂贵),在这种情况下会发生相同的过程,但NSObject会窃取并锁定旋转锁定为SideTable
,将其引用计数增加2,然后解锁旋转锁定。整个过程由一个C函数_objc_rootRetain_slow
表示,如下所示:
id _objc_rootRetain_slow(id obj) {
SideTable *table = SideTable::tableForPointer(obj);
OSSpinLockLock(&table->slock);
table->refcnts[DISGUISE(obj)] += 2;
OSSpinLockUnlock(&table->slock);
return obj;
}
答案 2 :(得分:2)
当不使用ARC(或编写可在ARC或非ARC模式下使用的代码)时,我更喜欢以下样式:
在-createSonicBoom,
sonicBoom.damagedObjects = [NSMutableArray array]; // or e.g. arrayWithCapacity:10?
在dealloc中,
self.damagedObjects = nil;
有一个小问题:如果你已经覆盖了setter方法来做一些聪明的事情,它可能在-dealloc
中做错了。