cocos2d-xでメモリリークを防ぐ開発習慣

cocos2d-xはC++で開発するため、メモリ管理が自由にできてパフォーマンス最適化がしやすい反面、人為的ミスでメモリリークや解放処理ミスを起こしやすい環境だと言えます。

メモリリークや解放処理ミスは再現や修正が非常に難しいバグであり、開発工程終盤になればなるほど難易度が増します。
発生させたらすぐに発見できるような開発習慣をつけておくことが大切です。

この記事では、僕が良い習慣だと考えているものをあげます。
随時追記し説明を詳細化していく予定です。


①いろんなタイミングでシーンを開放してみる
Cocos2dxでシーンの解放は非常に簡単です。
今、GameSceneというシーンの中でのメモリリークを検知したいとしましょう。
適当なテスト用のシーンを用意しておき(GameOverScene)、GameScene内の任意の場所に以下を記載します。
CCDirector::sharedDirector()->replaceScene(GameOverScene::create());

これにより、GameSceneを開放した上でGameOverSceneに移行します。
シーン内で確保したメモリが全て解放されていればメモリリーク無しといえます。
解放処理に重大なミスがあると、EXC_BAD_ACCESSなどの例外で停止します。

常にメモリ使用量を表示しておくのも有効です。
GameScene起動前と解放後のメモリ使用量が異なっている場合はメモリリークが起きている可能性があります。

デストラクタは、いつ呼ばれようとも確保したメモリは全て解放できるように書くのがベストです。
いろいろな場所に上記のシーン移動処理を入れてみて、メモリリークが発生しないことを確認するといいでしょう。

メモリ使用量表示方法はsyuhariさんが紹介しておられます。
http://blog.syuhari.jp/archives/2352
こちらでは、CCDirectorを修正する方法をとっていますが、Cocos2d内部のファイルには手を入れたくない方が多いと思います。
そのため、専用のCCLabelTTF派生クラスを作成しました。
このブログの最後に記載しますのでお使いください。





メモリリーク箇所探索の手助けにInstrumentsのLeaksツールを使う
http://tks2.net/memo/?p=122
清水さんが使い方を簡単に紹介しておられました。
僕はまだC++でInstrumentsをつかったことがないので、使ってみてまたこの記事で共有します。





■メモリ使用量(残りメモリ量)表示クラスMemoryUsedMetor

(2013-07-17追記:CCLabelTTFを毎フレーム描画更新しているので描画処理負荷が大きいという指摘をいただいています。)

・使い方
貼り付けたいLayerに対して以下でaddChildします。
MemoryUsedMetor* memoryMetor = MemoryUsedMetor::create();
memoryMetor->setAnchorPoint(ccp(1, 0));
memoryMetor->setPosition(ccp(CCDirector::sharedDirector()->getWinSize().width, 0));
layer->addChild(memoryMetor);


・MemoryUsedMetor.h

#ifndef __MemoryUsedMetor__
#define __MemoryUsedMetor__

#include
#include "cocos2d.h"
USING_NS_CC;

class MemoryUsedMetor : public CCLabelTTF {
private:
bool init();
void update(float delta);
double getAvailableBytes();
double getAvailableKiloBytes();
double getAvailableMegaBytes();
public:
CREATE_FUNC(MemoryUsedMetor);
~MemoryUsedMetor();
};

#endif /* defined(__MemoryUsedMetor__) */



・MemoryUsedMetor.cpp
#include "MemoryUsedMetor.h"
#include
#import
#import

bool MemoryUsedMetor::init() {
if (!CCLabelTTF::initWithString("000.0", "Arial", 48)) {
return false;
}

scheduleUpdate();
return true;
}

MemoryUsedMetor::~MemoryUsedMetor() {
unscheduleUpdate();
}

void MemoryUsedMetor::update(float delta) {
char megaBytes[12];
sprintf(megaBytes, "%.1f MB", getAvailableMegaBytes());
setString(megaBytes);
}

double MemoryUsedMetor::getAvailableBytes() {
vm_statistics_data_t vmStats;
mach_msg_type_number_t infoCount = HOST_VM_INFO_COUNT;
kern_return_t kernReturn = host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vmStats, &infoCount);
if (kernReturn != KERN_SUCCESS) {
return 0.0f;
}

return (vm_page_size * vmStats.free_count);
}

double MemoryUsedMetor::getAvailableKiloBytes() {
return getAvailableBytes() / 1024.0f;

}

double MemoryUsedMetor::getAvailableMegaBytes() {
return getAvailableKiloBytes() / 1024.0f;
}