ナクナイ

勉強用の備忘録

[C++][cocos2d-x] Assert failed: reference count should greater than 0 や Thread 1:EXC_BAD_ACCESS のエラー

cocos2d-x でアプリ開発をしているとき、タイトルのようなエラーになってはまったので、その解決方法をメモ。

シーンからシーンへ切り替える際、都度 replaceScene を書きたくなかったので、
シーン切り替え用の関数を下記のように作成。


ヘッダーファイルの宣言 (BaseLayer.h)

class BaseLayer : public cocos2d::Layer
{
public:
    void gotoNextScene(float time);
    void setNextScene(cocos2d::Scene *scene);
    
private:
    cocos2d::Scene *next_scene;
};


cpp ファイルの定義 (BaseLayer.cpp)

void BaseLayer::gotoNextScene(float time)
{
    cocos2d::TransitionCrossFade *crossFade = cocos2d::TransitionCrossFade::create(0.5f, this->next_scene);
    cocos2d::Director::getInstance()->replaceScene(crossFade);
}

void BaseLayer::setNextScene(cocos2d::Scene *next_scene)
{
    this->next_scene = next_scene;
}


呼び出しもとの cpp ファイル (SplashScene.cpp)

bool SplashScene::init()
{
    if ( !Layer::init() )
    {
        return false;
    }
    
    Size visibleSize = Director::getInstance()->getVisibleSize();
    Point origin = Director::getInstance()->getVisibleOrigin();

    // スプラッシュ画面の生成
    auto label = LabelTTF::create("splash", "Arial", 80);
    label->setPosition(Point(origin.x + visibleSize.width/2,
                             origin.y + visibleSize.height - label->getContentSize().height));
    this->addChild(label, 1);
    
    // ↓ここ↓
    this->setNextScene(StartScene::createScene());
    this->scheduleOnce(schedule_selector(StartScene::gotoNextScene), 3);

    return true;
}

こんな感じで実行したら、ビルドは通るのに実行するとエラーになってしまった。
しかも、実行するたびにたまにエラー内容が変化するという悲しい現象。具体的には、

cocos2d/cocos/base/CCRef.cpp

void Ref::retain()
{
     CCASSERT(_referenceCount > 0, "reference count should greater than 0"); // ←ここ                                    
     ++_referenceCount;
}

で引っかかって

cocos2d: Assert failed: reference count should greater than 0
Assertion failed: (_referenceCount > 0), function retain, file

のエラーが出たり・・・・

cocos2d/cocos/2d/CCTransition.cpp

// custom onEnter
void TransitionScene::onEnter()
{
    Scene::onEnter();

    // disable events while transitions
    _eventDispatcher->setEnabled(false);

    // outScene should not receive the onEnter callback
    // only the onExitTransitionDidStart
    _outScene->onExitTransitionDidStart();

    _inScene->onEnter(); // ←ここ                                                                                    
}

でひっかかって

Thread 1:EXC_BAD_ACCESS (code=2, address=0xb0000160)

のようなエラーが出たり・・・。


ぐぐって調べていくと、どうやらメモリ管理がうまくできてないことが原因ということはあたりがついた。

最初のエラーに関しても、リファレンスカウントに関するエラーなので、おそらくすでにリリース済みのメモリ(もしくははじめから保持に失敗しているメモリ)に対して、メモリを取得しようとしている、ということのはず。


そこで、明示的にリファレンスカウントを retain(あげる)、release(さげる) してあげることで解決した。


具体的には、SplashScene.cpp を下記を追記。

void BaseLayer::gotoNextScene(float time)
{
    cocos2d::TransitionCrossFade *crossFade = cocos2d::TransitionCrossFade::create(0.5f, this->next_scene);
    cocos2d::Director::getInstance()->replaceScene(crossFade);
    this->next_scene->release(); // ここで release
}

void BaseLayer::setNextScene(cocos2d::Scene *next_scene)
{
    this->next_scene = next_scene;
    this->next_scene->retain(); // ここで retain
}


ただし、
iOS 開発で、EXC_BAD_ACCESS とさよならするための6つのルール | Zero4Racer PRO Developer's Blog
を見ると正しい対応ではない予感はすごくしている。

とりあえずいまはこのばんそこ対応で我慢して、もっと勉強して理解が深まったらちゃんと解決するとしよう。。
有識者の方いらっしゃったらコメント欄に残していただけるとうれしいです。


おもに参考にしたページ

  • リファレンスカウントの概念について

参照回数によるメモリ管理

  • iOS のメモリ管理について

iOS 開発で、EXC_BAD_ACCESS とさよならするための6つのルール | Zero4Racer PRO Developer's Blog