Google+ もご覧ください
ユーザーアイコン

cocos2d javascript bindingsでマルチプラットフォーム開発

第十二回 オブジェクトライフサイクルについて

Seasons

第十二回 オブジェクトライフサイクルについて

第1011回とデバッグに関する手法について解説してきました。第11回執筆時点では、未対応でしたが、最新のcocos2d-x v3.0 beta版では、StepIntoのデバッグにも対応しています。これでJSBにおけるデバッグ環境は、通常のネイティブな開発環境とそう変わらない環境が提供されたと言っても良いでしょう。 しかし、これだけのデバッグ環境が提供されてもJSB特有の問題に遭遇することがあります。今回はその中でもオブジェクトのライフサイクルについて触れていきたいと思います。

オブジェクトの管理とライフサイクル

cocos2d-x JSBでは、第5回 this._super()とネイティブオブジェクトについて学ぶでもふれた通りネイティブなオブジェクトとJSのオブジェクトをプロキシオブジェクトが間に入る形で管理されています。一見するとこれでうまく管理されているように思えますが実はそうではありません。

JavaScriptとC,C++の異なる言語同士のオブジェクトのライフサイクルは元々異なっているため、片方が消滅したタイミングでもう片方が消滅するとは限りません。

JavaScriptオブジェクト

JavaScriptは、ガベージコレクションで管理されており、明示的にオブジェクトを破棄することをほとんどしません。利用されなくなったタイミングでオブジェクトが破棄される方式がとられています。

C,C++オブジェクト

一方、C/C++で記述されるcocos2dのオブジェクトは、メインループ内でオブジェクトのライフサイクルが管理されており、特にcreate系のメソッドで作成したオブジェクトは、autoreleaseオブジェクトとして生成され、図のようにそのフレーム内でのみ利用可能です。このオブジェクトはメインループ内で自動的に破棄される仕組みとなっています。

retainとrelease

先ほどのC/C++で生成されるネイティブオブジェクトにおいて、autorelease以外のオブジェクトは、retain、releaseというメソッドを用いて、オブジェクトのライフサイクルが制御されます。 これらは、objective-cのretain、releaseと同じ仕組みです。オブジェクト生成時は1、破棄したいタイミングでreleaseを実行し、ライフカウントを減らします。最終的に0になった時点でオブジェクトは、解放されます。

繰り返しになりますが、JSBではネイティブオブジェクトとJSオブジェクトをプロキシオブジェクトが管理するというライフサイクルを取っている都合上、ネイティブオブジェクトが消滅する時にJSオブジェクトを破棄する仕組みを採用しています。

利用者にはあまりこれらを意識せずにコーディングできるように、JSだけでコードを書いていれば内部ではネイティブオブジェクトが生成され、適切なタイミングでオブジェクトが破棄されるようになっています。

ライフサイクルが破綻する特殊なケース

公式の解説記事1でも触れられていますが、このJSBのライフサイクルの管理の都合上、うっかりやらかしてしまい、わかりづらいバグを引き起こします。

これは、ある関数内での処理の一部ですが、ScaleToオブジェクトを生成した後、このオブジェクトのプロパティactionにセットしているコードです。

        var scale = cc.ScaleTo.create(1.0, 10.0, 10.0);
        this.action = scale;

        return true;
    },

一見すると特に問題なさそうなのですが、実はここでセットされたthis.actionのScaleToオブジェクトに紐付いたネイティブオブジェクト(ScaleTo)は関数を抜けてメインループの中で破棄されてしまいます。そのため、別の関数でこのthis.actionを利用しようとしても、すでに破棄されているオブジェクトであるため処理を実行することができません。

これがcocos2d-html5であれば、JavaScriptだけで構成されるフレームワークなため、this.actionの指すScaleToオブジェクトは生存し続け、別の関数でも実行することができます。

そのため、cocos2d-html5で作成したソースコードをJSBに移植するとこのようなオブジェクトのライフサイクルの管理の違いによりプログラムがクラッシュすることがあります。

この問題に対する解決策は、2つあります。

1.addChildでオブジェクトを登録する 2.retainメソッドでライフカウントを増加させる

1.addChildでオブジェクトを登録する

今回のようなthisのプロパティにオブジェクトを代入する方法では、autorelease生成されたオブジェクトの場合、どうしても次のフレームでは自動消滅しているため、this.addChildでオブジェクトを一時的に登録してしまうという方法です。addChild内では、登録されたオブジェクトがretainされるため、追加先の親の生存期間の間、オブジェクトは生存し続けます。しかし、addChildできるものとそうでないものがあったりするため、どうしても利用ケースは限られてきます。

例えばスプライト(cc.Sprite)は、addChildした後、すぐに使わないのであれば、setVisible(false)で非表示にしておくというテクニックが利用できます。

2.retainメソッドでライフカウントを増加させる

各オブジェクトには、retain/releaseメソッドが実装されているため、オブジェクトの生存期間を延命させておきたい場合は、retainを呼び出しライフカウントが1つ増加させておきます。こうすることで関数を抜けても生存し続ける事ができます。先ほどのコードを用いると次のようなソースコードになります。

        var scale = cc.ScaleTo.create(1.0, 10.0, 10.0);
        this.action = scale;
        this.action.retain();

        return true;
    },

    onExit: function() {
        this.action.release();
        this._super();
    }

注意点としてretainしてしまうと、releaseしない限りはメモリに残り続けてしまうため、上記のようなonExitでそのクラスが破棄されるタイミングで同時に破棄されるようにしておきましょう。また、releaseはしたものの、再度アクセスされては困る場合はreleaseした後、nullを代入しておくのも有効でしょう。

サンプルコードは、こちらからダウンロード(Gist)できます。

retain/releaseはJSBでは関係ないことだと思いがちですが実は今回のように実装の都合上どうしても必要になってくるため、このことを予め理解した上でコーディングするように心がけるようにしてください。

次回予告

次回は、cocos2d-x jsbにおける開発Tipsの1つを紹介したいと思います。

cocos2d-x:メモリーマネジメントJSB


Cocos2d jsb

記事をリクエストする

関連記事

コメント