[cocos2dx][CocosBuilder]CocosBuilderのメモリリークポイント

 一つ前の投稿でCocosBuilderに関連するメモリリークの話をしましたが、あれからまたメモリ周りのクソ仕様(不具合ともいう)に遭遇したので、(いないと思うけど)これからCocosBuilderを使おうとしている皆様が同じ轍を踏まないよう、ここに記録しておきたいと思います。

悪いのはCCBReader

 メモリ周りの問題はCocosBuilder、というよりCCBReaderの実装に問題があるために発生します。CCBReaderのソースを読んだことがある方はお気づきかと思いますが、CCBReaderの実装は非常に雑です。CocosBuilderはもともとcocos2d向けのツールなので仕方ない部分もあると思いますが、どうやったらそうなるんだ的なコードがあちこちに散らばっており、いよいよ地獄の様相です。

メモリリークの原因1、CCBAnimationManager

 まずはじめに、前回の記事で若干勘違い気味に触れたメモリリークに関してです。
 前回の記事では、readNodeGraphFromFileで生成したノードの参照カウントが1増えていると記述しましたが、あれは間違いです。毎度毎度間違った情報を垂れ流してしまって申し訳ないです。
 上記の内容は、正確にはCCBAnimationManager::setDelegate内でretainが呼ばれていることに起因します。

CCBReader reader(CCNodeLoaderLibrary::sharedCCNodeLoaderLibrary());

Hoge *hoge = static_cast<Hoge *>(reader.readNodeGraphFromFile("hoge.ccbi"));
hoge->setAnimationManager(reader.getAnimationManager());
reader.getAnimationManager()->setDelegate(hoge); // ←これ!

ここです。このsetDelegateの中でretainが呼ばれている(上記例では「hoge」に対してretainが使用されている)ので、hogeの参照カウントが1増えてしまいます。したがって、addChildの後にでもreleaseを書いておきましょう。

CCBReader reader(CCNodeLoaderLibrary::sharedCCNodeLoaderLibrary());

Hoge *hoge = static_cast<Hoge *>(reader.readNodeGraphFromFile("hoge.ccbi"));
hoge->setAnimationManager(reader.getAnimationManager());
reader.getAnimationManager()->setDelegate(hoge);
addChild(hoge);

hode->release(); // 参照カウントを減らす

こんな感じです。

メモリリークの原因2、CCB_MEMBERVARIABLEASSIGNER_GLUE

 非常に長ったらしい邪悪なマクロです。CCB_MemberVariableAssigner_Glueと名のある通り、CocosBuilder側のノード類をcocos2dx側の変数にバインドするために使用します。使う場所等に関しては適宜ググってください。すぐ見つかります。問題は、このマクロの中でretainが呼ばれることです。

#define CCB_MEMBERVARIABLEASSIGNER_GLUE(TARGET, MEMBERVARIABLENAME, MEMBERVARIABLETYPE, MEMBERVARIABLE) \
    if (pTarget == TARGET && 0 == strcmp(pMemberVariableName, (MEMBERVARIABLENAME))) { \
        MEMBERVARIABLETYPE pOldVar = MEMBERVARIABLE; \
        MEMBERVARIABLE = dynamic_cast<MEMBERVARIABLETYPE>(pNode); \
        CC_ASSERT(MEMBERVARIABLE); \
        if (pOldVar != MEMBERVARIABLE) { \
            CC_SAFE_RELEASE(pOldVar); \
            MEMBERVARIABLE->retain(); // ←ここ! \
        } \
        return true; \
    }

 邪悪ですねー実に邪悪です。しっかりretainされているのでreleaseする必要がありますが、原因1と違いすぐにreleaseしてしまうと参照カウントが0になってしまい非常によろしくありません。またマクロを見ても分かる通り、そもそもこのマクロの中でreturnするという邪悪極まりない実装のため、直後にreleaseを呼ぶことが出来ません。したがって、カスタムクラスのデストラクタでメンバを解放してやる必要があります。

CCB_MEMBERVARIABLEASSIGNER_GLUE(this, "hoge", CCSprite *, hoge);

みたいなコードがあったら、このメンバを持つクラスのデストラクタ内で

HogeLayer::~HogeLayer()
{
    hoge->release();
}

と書いてあげましょう。

まとめ

 以上が私が遭遇したメモリリークポイントです。おそらく他にもこっそりとretainしている場所があるのではないかと思います。そもそもcocos2dxが参照カウントという不適切なメモリ管理方法を用いていることも原因だと思います。cocos2dxのコードはとてもきたないので、いい加減まともな実装をしてほしいと思います(オープンソースなので仕方ないとは思いますけどね)。

0 件のコメント :

コメントを投稿