正确重启/重新初始化半单例

Posted

技术标签:

【中文标题】正确重启/重新初始化半单例【英文标题】:Properly restarting/re-initializing a semi-singleton 【发布时间】:2013-06-30 23:36:54 【问题描述】:

我在 cocos2d 游戏的主游戏层/场景中使用半单例方法,如后面的代码所示。

目标是通过调用 [[GameLayer sharedGameLayer] restart] 方法使用暂停或游戏结束层中的按钮正确重新启动/重新创建此单例场景。

问题是如果我为此使用 CCTransition 效果,并使用 sharedGameLayer = nil; 行覆盖 GameLayer 的 dealloc 方法(到确保静态变量的重置),sharedGameLayer 变量在第一次重启后(也就是第一次 dealloc 后)保持为零,所以调用 restart 方法什么都不做。

怀疑的方法根本不覆盖 dealloc 方法,而是在使用 replaceScene: 重新启动场景之前,我设置sharedGameLayer 为零。

问题:这是重新启动/重新创建这个半单例类的正确方法吗?

提前致谢。

代码:

GameLayer.m:

static GameLayer *sharedGameLayer;

@implementation GameLayer

- (id)init

    NSLog(@"%s", __PRETTY_FUNCTION__);

    // always call "super" init
    // Apple recommends to re-assign "self" with the "super's" return value
    if (self = [super initWithColor:ccc4(255, 255, 255, 255) width:[CCDirector sharedDirector].winSize.width
                             height:[CCDirector sharedDirector].winSize.height])
    

        // Set the sharedGameLayer instance to self.
        sharedGameLayer = self;

        // Set the initial game state.
        self.gameState = kGameStateRunning;

        // Register with the notification center in order to pause the game when game resigns the active state.
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
        [center addObserver:self selector:@selector(pauseGame) name:UIApplicationWillResignActiveNotification object:nil];

        // Enable touches and multi-touch.
        CCDirector *director = [CCDirector sharedDirector];
        [director.touchDispatcher addTargetedDelegate:self priority:0 swallowsTouches:YES];
        director.view.multipleTouchEnabled = YES;


        // Set the initial score.
        self.score = 5;

        // Create the spiders batch node.
        [self createSpidersBatchNode];

        // Load the game assets.
        [self loadAssets];


        // Play Background music.
        if (![SimpleAudioEngine sharedEngine].isBackgroundMusicPlaying)
            [[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"backgroundmusic.mp3" loop:YES];

        // Preload sound effects.
        [[SimpleAudioEngine sharedEngine] preloadEffect:@"inbucket.mp3"];
        [[SimpleAudioEngine sharedEngine] preloadEffect:@"outbucket.mp3"];
        [[SimpleAudioEngine sharedEngine] preloadEffect:@"gameover.mp3"];

        // Schdule updates.
        [self scheduleUpdate];
        [self schedule:@selector(releaseSpiders) interval:0.7];
    

    return self;




- (void)createSpidersBatchNode

    NSLog(@"%s", __PRETTY_FUNCTION__);

    // BatchNode. (For Animation Optimization)
    self.spidersBatchNode = [CCSpriteBatchNode batchNodeWithFile:@"spiderAtlas.png"];

    // Spider sprite + BatchNode + animation action.
    for (int i = 0; i < 50; i++) 
        Spider *spider = [[Spider alloc] init];
        spider.spiderSprite.visible = NO;
    

    [self addChild:self.spidersBatchNode];




- (void)restartGame

    // Play button pressed sound effect.
    [[SimpleAudioEngine sharedEngine] playEffect:@"button.mp3"];

    // Nil'ing the static variable.
    sharedGameLayer = nil;

    // Restart game.
    [[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0 scene:[GameLayer scene]]];




+ (CCScene *)scene

    // 'scene' is an autorelease object.
    CCScene *scene = [CCScene node];

    // Game Layer.
     // 'gameLayer' is an autorelease object.
    GameLayer *gameLayer = [GameLayer node];

     // add gameLayer as a child to scene
    [scene addChild: gameLayer];

    // HUD Layer.
    HUDLayer *hudLayer = [HUDLayer node];
    [scene addChild:hudLayer];
    gameLayer.hud = hudLayer;

    // return the scene
    return scene;




+ (GameLayer *)sharedGameLayer

    return sharedGameLayer;




- (void)cleanup

    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [[CCDirector sharedDirector].touchDispatcher removeDelegate:self];
    [self stopAllActions];
    [self unscheduleAllSelectors];
    [self unscheduleUpdate];

    [self removeAllChildrenWithCleanup:YES];
    self.hud = nil;

    [super cleanup];




//- (void)dealloc
//
//    sharedGameLayer = nil;
//


@end

【问题讨论】:

【参考方案1】:

我敢打赌,您的新游戏层是在第一个释放之前创建的,因此它将静态 var 设置为 nil。检查 sharedGameLayer 在 dealloc 中是否等于 self。

【讨论】:

感谢您的回复。我在前面的问题中说过,在 GameLayer 场景的第一次重新启动/dealloc 之后,如果 -deallocsharedGameLayer = nil; 行覆盖,则 sharedGameLayer 变量始终为零。还是您的意思是我应该在-dealloc 方法中将变量设置为self?这听起来很奇怪,因为您在书中的 -init 方法中将其设置为 self。【参考方案2】:

不要使用 dealloc 将静态变量设置为 nil。 Dealloc 发生在您的对象停止使用之后。切勿使用它来控制应用的行为。

就您所知,从您将sharedGameLayer 设置为nil 到调用dealloc 方法之间可能已经过了30 分钟。或者dealloc 可能根本不会被调用

您的代码看起来不错,只需删除注释掉的“dealloc”代码即可。

另外,这段代码:

[[NSNotificationCenter defaultCenter] removeObserver:self];

除了您的自定义-cleanup 方法外,还应在-dealloc 方法中完成。移除观察者两次是完全没问题的,如果它已经被移除,则不会发生任何事情。

另外注意,+sharedGameLayer 应该返回一个instancetype 类型的变量,它应该负责设置sharedGameLayer 变量。如果你在-init 中使用sharedGameLayer,就会遇到错误。

例如:

+ (instancetype)sharedGameLayer

    if (sharedGameLayer)
        return sharedGameLayer;

    sharedGameLayer = [[[self class] alloc] init];

    return sharedGameLayer;


- (id)init

    if (!(self = [super init]))
        return nil;

    .
    .
    .

    return self;

【讨论】:

感谢您的回复。我在问题中编辑了我发布的代码以包含完整的 -init 方法,并且您可以看到有一个 -createSpidersBatchNode 方法实际上调用了一个蜘蛛类来创建batchNode 中的蜘蛛实际上包含在 GameLayer 的类本身中,因此发生了一个循环 (-init -> -createSpidersBatchNode -> Spider 的类 -> [[GameLayer sharedGameLayer].spidersBatchNode addchild:] -> sharedGameLayer -> -init- ..etc)。那是因为 sharedGameLayer 从来没有机会被设置为自我思考。

以上是关于正确重启/重新初始化半单例的主要内容,如果未能解决你的问题,请参考以下文章

单例的正确初始化方法

正确使用和实现单例

单例模式正确使用方式

单例模式正确使用方式

初始化时的用户单例和 nsuserdefault

单例模式