cocos2d-xにおいては、すべてのクラスは
cocos2d::Ref
の子孫クラスである。
Refクラスは、参照カウンタを扱っている。
Refクラスのautorelease()メソッドを呼んでおくと、
Refとその子孫クラスのインスタンスを生成したとき、破棄処理を自分でしなくても
ゲームのメインループが来たときに自動で破棄してくれる。
CCRef.cpp
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
CCDirector.cpp
void DisplayLinkDirector::mainLoop()
{
if (_purgeDirectorInNextLoop)
{
_purgeDirectorInNextLoop = false;
purgeDirector();
}
else if (! _invalid)
{
drawScene();
// release the objects
PoolManager::getInstance()->getCurrentPool()->clear();
}
}
CCAutoReleasePool.cpp
void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = true;
#endif
for (const auto &obj : _managedObjectArray)
{
obj->release();
}
_managedObjectArray.clear();
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = false;
#endif
}
CCRef.cpp
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should greater than 0");
--_referenceCount;
if (_referenceCount == 0)
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
auto poolManager = PoolManager::getInstance();
if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
{
// Trigger an assert if the reference count is 0 but the Ref is still in autorelease pool.
// This happens when 'autorelease/release' were not used in pairs with 'new/retain'.
//
// Wrong usage (1):
//
// auto obj = Node::create(); // Ref = 1, but it's an autorelease Ref which means it was in the autorelease pool.
// obj->autorelease(); // Wrong: If you wish to invoke autorelease several times, you should retain `obj` first.
//
// Wrong usage (2):
//
// auto obj = Node::create();
// obj->release(); // Wrong: obj is an autorelease Ref, it will be released when clearing current pool.
//
// Correct usage (1):
//
// auto obj = Node::create();
// |- new Node(); // `new` is the pair of the `autorelease` of next line
// |- autorelease(); // The pair of `new Node`.
//
// obj->retain();
// obj->autorelease(); // This `autorelease` is the pair of `retain` of previous line.
//
// Correct usage (2):
//
// auto obj = Node::create();
// obj->retain();
// obj->release(); // This `release` is the pair of `retain` of previous line.
CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");
}
#endif
#if CC_USE_MEM_LEAK_DETECTION
untrackRef(this);
#endif
delete this;
}
}
CCAutoReleasePool.cppを見るとわかるが、_managedObjectArrayに格納されているすべてのobjectに対してrelease()を実行している。
ずいぶん潔いやり方だ。生成しても放っておいてよい、というか放っておかなくてはいけない。自分でrelease()とか呼ぶと却ってバグる。
その使い方についてはCCRef.cppのrelase()メソッド内に詳しく書いてある。
一言で言うと
new/retain() と autorelease()/release() を対で呼び出さなければならない。ということだ。
生成時にnewが呼ばれる。
cocos的には、Node::create()やSprite::create()などのように、static関数のcreate()メソッドが相当。create()の中でnewをしている。
例としてNode::create()を見てみると
Node * Node::create()
{
Node * ret = new Node();
if (ret && ret->init())
{
ret->autorelease();
}
else
{
CC_SAFE_DELETE(ret);
}
return ret;
}
となっている。
createの中で new と autorelease() を両方やっている。
それ以外にも、retain()を呼ぶならrelease()かautorelease()をどこかで呼んでおく。
対で呼び出されるようになっていれば、メモリリークは起こらない。
autorelease()は複数回呼び出しても問題ないようだ。autoreleasePoolに2回登録されるので、2回releaseが走る。
なお、Nodeインスタンスの場合、別のNodeの子になる(addChild)と参照カウンタが1増える。
parentNode->addChild(childNode); //childNodeの参照カウンタが1増える
これは、parentNodeのメンバ変数
Vector<Node*> _children;
に追加されるから。
cocos2d-xのVectorクラスは、pushBack(またはinsert,replace)したときにretain()を呼ぶようになっている。
このため、addChildされた子ノードは破棄されずに残る。
PR