了解iphone cocos2d分配系统的问题

时间:2012-01-03 10:12:35

标签: iphone objective-c memory-management cocos2d-iphone

我正在尝试按照以下书籍http://www.apress.com/9781430233039学习iphone的cocos2d。我修改了源代码的CH_08文件夹中提供的ShootEmUp3示例(http://www.apress.com/downloadable/download/sample/sample_id/640/)。

我想要实现的是拥有菜单场景和游戏场景,并且一旦游戏场景结束就能够返回到菜单场景(例如,所有生命都丢失)。从菜单场景我可以通过点击名为“LevelIcon”的类的图标来访问特定的GameScene。换句话说,菜单场景,我称之为“Navigator”(CCLayer的子类),包含出现在菜单场景中的“LevelIcon”类(NSObject的sublcass),并通过调用CCDirector的replaceScene方法响应触摸事件,如下:。

//From LevelIcon.m    
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
    CCLOG(@"Touch");
    [[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
    GameScene * game = [GameScene scene];
    [[CCDirector sharedDirector] replaceScene:game];
    return TRUE;
}

我试图在Navigator dealloc方法中释放对象,但是在LevelIcon的release方法上添加CCLOG消息我发现从不调用release方法。

//From Navigator.m

-(void)dealloc
{
    CCLOG(@"Navigator dealloc");
    CCLOG(@"%@: %@", NSStringFromSelector(_cmd), self);

    level1 = nil;
    [level1 dealloc];

    [super dealloc];
}

然后我在更新方法中添加了一个日志,并观察到即使场景是GameScene而不是Navigator场景,它仍然被调用。我无法理解的是LevelIcon是在Navigator中创建的对象,在Navigator的dealloc方法中,我尝试释放类的istance,如上所示(但不执行)。

作为对此的确认,我尝试从GameScene返回并转发到Navigator,以便在GameScene中触发以下代码:

        Navigator * navigator = [Navigator scene];
    [[CCDirector sharedDirector] replaceScene:navigator];

创建一个新的Navigator类可以正常工作,但是当回到GameScene时,新的LevelIcon类实例不会消失,而是保留(以便它累积)。我们必须注意到,在LevelIcon类中调用replaceScene方法,响应触摸事件,如下所示:

GameScene * game = [GameScene scene];
[[CCDirector sharedDirector] replaceScene:game];

换句话说,在此事件之后调用Navigator释放方法而不是LevelIcon,并且如果LevelIcon类多次在内存中保持活动状态。

我不确定我做错了什么,但会感激一些提示或帮助。

我还尝试了一种不同的方法,通过添加LevelIcon作为Navigator的子级(使用addChild方法),但它不起作用,因为它是从NSObject类而不是cocos2d Node类派生的。我想知道以某种方式调整它是否有意义,或者它是否与两个不同的类层次结构(cocos2d和NS / Objective-C)不同。

我粘贴完整的代码以帮助理解:

--NAVIGATOR.H-----

#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "LevelIcon.h"

@interface Navigator : CCLayer {
    LevelIcon *level1;
}

+ (id) scene;

@end


-----NAVIGATOR.M------
#import "Navigator.h"
#import "LevelIcon.h"

@implementation Navigator

+(id) scene {
    CCScene *scene = [CCScene node];
    CCLayer *layer = [Navigator node];//??
    [scene addChild:layer];
    return scene;     
}

-(id)init 
{
    CCLOG(@"init");
    if((self=[super init])){        
        CCLOG(@"%@: %@", NSStringFromSelector(_cmd), self);
        self.isAccelerometerEnabled=YES;
        self.isTouchEnabled = YES;

        [self scheduleUpdate];  
        level1 = [LevelIcon levelIconWithParentNode:self];
    }
    return self;
}

-(void)dealloc
{
    CCLOG(@"Navigator dealloc");
    CCLOG(@"%@: %@", NSStringFromSelector(_cmd), self);

    level1 = nil;
    [level1 dealloc];

    [super dealloc];
}
[..]
@end


------LEVELICON.H------------
#import <Foundation/Foundation.h>
#import "cocos2d.h"

@interface LevelIcon : NSObject<CCTargetedTouchDelegate> {
    CCSprite* levelIconSprite;
 }

+(id) levelIconWithParentNode:(CCNode*)parentNode;
-(id) initWithParentNode:(CCNode*)parentNode;

@end



-----LEVEL ICON.M----------------
#import "LevelIcon.h"
#import "GameScene.h"

@implementation LevelIcon

// Static autorelease initializer, mimics cocos2d's memory allocation scheme.
+(id) levelIconWithParentNode:(CCNode*)parentNode
{
    CCLOG(@"levelIconWithParentNode");  
    return [[[self alloc] initWithParentNode:parentNode] autorelease];
}

-(id) initWithParentNode:(CCNode*)parentNode 
{
    CCLOG(@"initWithParentNode");
    if ((self = [super init]))
    {
        CCLOG(@"initWithParentNode: inside if");
        CGSize screenSize = [[CCDirector sharedDirector] winSize];

        levelIconSprite = [CCSprite spriteWithFile:@"Icon.png"];
        levelIconSprite.position = CGPointMake(CCRANDOM_0_1() * screenSize.width, CCRANDOM_0_1() * screenSize.height);
        [parentNode addChild:levelIconSprite];

        // Manually schedule update via the undocumented CCScheduler class used internally by CCNode.
        [[CCScheduler sharedScheduler] scheduleUpdateForTarget:self priority:0 paused:NO];

        // Manually add this class as receiver of targeted touch events.
        [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:-1 swallowsTouches:YES];
    }

    return self;
}

-(void) dealloc
{
    CCLOG(@"Level icon dealloc");
    // Must manually unschedule, it is not done automatically for us.
    [[CCScheduler sharedScheduler] unscheduleUpdateForTarget:self];

    // Must manually remove this class as touch input receiver!
    [[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
    [super dealloc];
}

-(void) update:(ccTime)delta
{
    CCLOG(@"Icon Update!");
}


-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
    CCLOG(@"Touch");
    [[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
    GameScene * game = [GameScene scene];
    [[CCDirector sharedDirector] replaceScene:game];
    return TRUE;
}

@end

感谢阅读! :)

2 个答案:

答案 0 :(得分:2)

这很奇怪:

level1 = nil;
[level1 dealloc];

1)你不想直接调用dealloc,使用[level1 release],当引用计数达到0时,它将自动解除分配

2)这个顺序错了,在将level1设置为nil后,下一行无效(你可以无害地向nil指针发送消息,它们悄悄地什么也不做)。

答案 1 :(得分:0)

在一天结束时,我发现错误不是由调用发布对象直接引起的。

上面的代码显示了自动释放初始化,因此不需要调用该版本。我同意,我试图在导航器中再次调用release时出现错误。没有任何借口(唯一的一个是我对此的无知并试图在没有成功的情况下做到这一点并没有成功确实导致我试图以错误的方式玩游戏而不信任鲜为人知的'理论'并试图以更关键的方式找到问题)。

问题是该对象“以某种方式”保留了两次,因此dealloc从未被调用过。

为了解决这个问题,我添加了对

的调用
[[CCScheduler sharedScheduler] unscheduleUpdateForTarget:self];
[[CCTouchDispatcher sharedDispatcher] removeDelegate:self];

进入 ccTouchBegan 方法,以便丢失可能的保留。

在方法 removeDelegate 的定义中,没有显式的释放调用,但是 在 sharedDispatcher addTargetedDelegate 方法中,调用 ccArrayAppendObject ,它保留作为参数传递的对象(参见下面的代码)。

//From CCTouchDispatcher.m line 121
-(void) addTargetedDelegate:(id<CCTargetedTouchDelegate>) delegate priority:(int)priority swallowsTouches:(BOOL)swallowsTouches 
{
    CCTouchHandler *handler = [CCTargetedTouchHandler handlerWithDelegate:delegate priority:priority swallowsTouches:swallowsTouches];
    if( ! locked ) {
        [self forceAddHandler:handler array:targetedHandlers];
    } else {
        [handlersToAdd addObject:handler];
        toAdd = YES;
    }
}

//From ccCArray.h line 115 (after having opened the definition of several function starting from **addObject** being called by addTargetedDelegate I got here)
/** Appends an object. Bahaviour undefined if array doesn't have enough capacity. */
static inline void ccArrayAppendObject(ccArray *arr, id object)
{
    arr->arr[arr->num] = [object retain];
    arr->num++;
}

然后查看 removeDelegate 的定义(请参阅下面的代码)现在在 ccTouchBegan 中调用的函数我发现 sharedDispatcher] removeDelegate 已添加该对象到 handlersToRemove 列表,然后释放。这应该是导致对象被释放的原因。

//From LevelIcon.m 
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
    CCLOG(@"Touch");
    [[CCScheduler sharedScheduler] unscheduleUpdateForTarget:self];
    [[CCTouchDispatcher sharedDispatcher] removeDelegate:self];

    GameScene * game = [GameScene scene];
    [[CCDirector sharedDirector] replaceScene:game];



    return TRUE;
}

//From CCTouchDispatcher.m line 153
-(void) removeDelegate:(id) delegate
{
    if( delegate == nil )
        return;

    if( ! locked ) {
        [self forceRemoveDelegate:delegate];
    } else {
        [handlersToRemove addObject:delegate];
        toRemove = YES;
    }
}