I’ve got a class called GamePlay, which looks somewhat like this:
@implementation GamePlay
-(id)init{
if((self = [super init])){
self.isAccelerometerEnabled = YES;
//ship defined in header
ship = [Ship shipWithParentNode:self];
CGSize screenSize = [[CCDirector sharedDirector] winSize];
float imageHeight = [ship spriteContentSize].height;
[ship setSpritePosition:CGPointMake(screenSize.width*0.5, imageHeight*0.5)];
}
return self;
}
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration{
CGPoint pos = [ship spritePosition];
pos.x = acceleration.x * 10;
[ship setSpritePosition:pos];
}
@end
So, the Ship class looks something like this:
@implementation Ship
+(id)shipWithParentNode:(CCNode *)parentNode{
return [[[Ship alloc] initWithParentNode:parentNode] autorelease];
}
-(id)initWithParentNode:(CCNode *)parentNode{
if((self = [super init])){
sprite = [CCSprite spriteWithFile:@"ship.png"];
SpriteLoader *spriteLoader = [SpriteLoader sharedSpriteLoader];
[spriteLoader addTextureAtlas:@"Spites_default.plist"];
CCAnimation *spriteAnimation = [CCAnimation animationWithFrames:[spriteLoader getSpriteFramesWithName:@"ship-" andAmount:[NSNumber numberWithInt:5]] delay:0.08f];
CCAnimate *anim = [CCAnimate actionWithAnimation:spriteAnimation];
CCRepeatForever *repeat = [CCRepeatForever actionWithAction:anim];
[sprite runAction:repeat];
[parentNode addChild:sprite z:0 tag:SHIP];
}
return self;
}
-(void)setSpritePosition:(CGPoint)point{
sprite.position = point;
}
-(CGPoint)spritePosition{
return sprite.position;
}
-(CGSize)spriteContentSize{
return sprite.contentSize;
}
@end
GamePlay initialises the Ship fine and continues to modify the CCSprite within Ship, and in the simulator, all is well. When it comes to running it on a device, we get accelerometer input. In the accelerometer method, the first line crashed the program because the ship no longer exists. If I look in the debugger with a breakpoint on the first line of the accelerometer method I see the following:
self = (GamePlay *)
-> ship = (Ship *) 0x004701e0
If I then continue the running of the program, I get this message:
message sent to deallocated instance 0x4701e0
But I’m not sure why it’s been deallocated…And presumably 0x004701e0 == 0x4701e0 ?
Many thanks,
Ben
To explain the problem:
Ole is correct that the Ship needs a retain in the code you posted. Personally I see a design problem here. Your Ship class should derive from CCNode, and you should add the Ship to the node hierarchy via addChild.
If Ship derives just from NSObject, what you end up with is sort of like a “broken” node hierarchy:
The problem with that is that the Ship class’ sprite is “managed” by the Ship class but retained by the GamePlay class. That means you’re responsible for retaining the non-CCNode classes like Ship yourself. The following makes for a simpler and more Cocos2D conform design, because it relies on Cocos2D managing the entire node hierarchy:
That way you would add the Ship class as child to the GamePlay class. The Ship class adds its CCSprite instance to self (the Ship class). You can (and should) get rid of the parentObject passed in the shipWithParentNode method. In Cocos2D it is quite dangerous to pass an instance of a node to another node, you might be tempted to retain that, which can lead to leaking the entire scene because of circular retain dependencies (a retains b, b retains a, both can’t let go).
Overall this makes your node hierarchy a bit deeper but it is much easier to manage. You can assume all objects to derive from a common base class (CCNode) and you can rely on Cocos2D deallocating the node hierarchy in the right order at the right times.