cocos-js、メモリ管理1---参照カウント方式

6474 ワード

一.冒頭の引用問題.
cococos 2 dxのメモリ管理メカニズムを理解する前に、c++における変数のメモリ空間の割り当て問題を理解することができる.
私たちはc++にクラスを書いて、スタックにメモリ空間を割り当てることもnewを使ってスタックにメモリ空間を割り当てることもできます.クラスオブジェクトがスタックに割り当てられたメモリ空間であれば、このメモリ空間の管理は私たちのことではありませんが、スタックに割り当てられたメモリ空間であれば、もちろん手動のdeleteが必要です.
問題が発生しました.cocos 2 dxはメモリをどのように管理しますか.
cococos 2 dxはスタックにメモリ空間を割り当てることを採用しており、スタックにメモリ空間を割り当てる以上、このメモリ空間をどのように管理し、いつ解放すべきかが問題です.プログラムでは、オブジェクトを作成すると、このメモリ領域は常に異なるオブジェクトに参照されます.削除が早く、オブジェクトがこのメモリ領域を参照している場合、プログラムは必ずクラッシュします.従ってcococos 2 dxは参照カウントのメモリ管理メカニズムを導入した.
二.例の説明
1.c++コード例
//               1,              ,       Ref     
//Ref::Ref()
//: _referenceCount(1) 
Node * node = new Node();
("retain count:%d", node->getReferenceCount());

//  retain           1
node->retain();
log("retain count:%d", node->getReferenceCount());

//  release          1,         0   , release    delete     
node->release();
log("retain count:%d", node->getReferenceCount());

//     autorelease            
//PoolManager::getInstance()->getCurrentPool()->addObject(this);
//  autorelease                 ,                       release  
node->autorelease();
log("retain count:%d", node->getReferenceCount());

よく言われる自動回収メカニズム、つまり上のautoreleaseメソッドでは、autoreleaseメソッドを呼び出すと、オブジェクトがこのメモリ回収プールに配置され、フレームが終わるとこのメモリ回収プールが解放され、このときメモリ回収プールのオブジェクトがreleaseされ、つまり参照カウントが1減少します.このとき参照カウントが0の場合、オブジェクトは削除されます.参照カウントが0でないとオブジェクトは削除されない.
void DisplayLinkDirector::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();
    }
    else if (_restartDirectorInNextLoop)
    {
        _restartDirectorInNextLoop = false;
        restartDirector();
    }
    else if (! _invalid)
    {
        drawScene();

        // release the objects
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = true;
#endif
    std::vector releasings;
    releasings.swap(_managedObjectArray);
    for (const auto &obj : releasings)
    {
        obj->release();
    }
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = false;
#endif
}

次に、通常、私たちのコードがどのように書かれているかを見てみましょう.この自動回収メカニズムがどのように自動回収されているかを見てみましょう.
// create      sprite autorelease  
CCSprite * sprite = CCSprite::create("HelloWorld.png");
CCLog("retain count:%d",sprite->getReferenceCount()); //retain 1
this->addChild(sprite);
CCLog("retain count:%d",sprite->getReferenceCount()); //retain 2

まず上のコードを分析し、createファクトリメソッドを呼び出した後、内部の実装はまずnewのCCSpriteのオブジェクトであり、このときは参照カウントに1を加え、autoreleaseメソッドを呼び出し、このオブジェクトを自動回収プールに入れた.このフレームはまだ終わっていないので、もちろん参照カウントは1なので、印刷の結果は1である.addChildを呼び出すと、このCCSpriteオブジェクトが転送されます.このとき、現在のレイヤがこのオブジェクトを受け入れた後、参照カウントに1を加算し、現在のレイヤがこのメモリ領域を使用していることを示します.したがって、現在のretainは2になります.このフレームが終了すると自動回収プールはオブジェクトの参照数-1をカウントするので、CCLayerがこのオブジェクトを参照しているのは今だけです.CCLayerが解析すると、このオブジェクトのreleaseメソッドが呼び出され、このとき当然このCCSpriteオブジェクトが削除されます.自動回収メカニズムとは、このフレームの終了時にオブジェクトがnewを開始するときに加算される参照カウントを自動的に減らし、エンジンにオブジェクト参照を持つ他のクラスにこのオブジェクトを管理させ、所有者が解析するときに参照を削除し、エンジンのクラスがretainとreleaseを担当する、これも自動でしょう.次にcreateメソッドの実装を示します.
Node * Node::create()
{
    Node * ret = new (std::nothrow) Node();
    if (ret && ret->init())
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(ret);
    }

    return ret;
}

また、newのnodeの後にautoreleaseメソッドを呼び出す必要がなく、参照カウントを0にしてもいいですか?そうすれば、何気なくnewがnodeになった後、それを使うのを忘れてしまうと、自動的に回収されず、メモリを消費し続け、メモリが漏れてしまう可能性があります.
さらにaddChild時参照カウント+1,removeChild時参照カウント-1に関するコードを見てみましょう.
 addChild      this->insertChild(child, localZOrder);
 insertChild      _children.pushBack(child);
   pushBack   :
void pushBack(T object)
{
    CCASSERT(object != nullptr, "The object should not be nullptr");
    _data.push_back( object );
    object->retain();
}

2.JSコード例
まずJSのテンプレートエンジニアリングコードを見てみましょう.
var HelloWorldLayer = cc.Layer.extend({
    sprite:null,
    ctor:function () {
        //////////////////////////////
        // 1. super init first
        this._super();

        /////////////////////////////
        // 2. add a menu item with "X" image, which is clicked to quit the program
        //    you may modify it.
        // ask the window size
        var size = cc.winSize;

        // add a "close" icon to exit the progress. it's an autorelease object
        var closeItem = new cc.MenuItemImage(
            res.CloseNormal_png,
            res.CloseSelected_png,
            function () {
                cc.log("wade getReferenceCount1:"+helloLabel.getReferenceCount())
                cc.log("wade getReferenceCount2:"+this.sprite.getReferenceCount())
            }, this);
        closeItem.attr({
            x: size.width - 20,
            y: 20,
            anchorX: 0.5,
            anchorY: 0.5
        });

        var menu = new cc.Menu(closeItem);
        menu.x = 0;
        menu.y = 0;
        this.addChild(menu, 1);

        /////////////////////////////
        // 3. add your codes below...
        // add a label shows "Hello World"
        // create and initialize a label
        // var helloLabel = new cc.LabelTTF("Hello World", "Arial", 38);
        var helloLabel = cc.LabelTTF.create("Hello World", "Arial", 38);
        // position the label on the center of the screen
        helloLabel.x = size.width / 2;
        helloLabel.y = size.height / 2 + 200;
        // add the label as a child to this layer
        this.addChild(helloLabel, 5);

        cc.log("wade getReferenceCount1:"+helloLabel.getReferenceCount())

        // add "HelloWorld" splash screen"
        this.sprite = new cc.Sprite(res.HelloWorld_png);
        this.sprite.attr({
            x: size.width / 2,
            y: size.height / 2
        });
        this.addChild(this.sprite, 0);
        cc.log("wade getReferenceCount2:"+this.sprite.getReferenceCount())


        return true;
    }
});

getReferenceCountという方法は参照カウントを取得するものであり、Refクラスでは、上記の例についてカウントを出力と、この場合が2であることがわかる.しかし、私たちが閉じるボタンをクリックすると、参照カウントはすべて1になり、作成時にautoreleaseメソッドが呼び出されたことを説明し、JSの下層対cocosエンジンはすでに私たちのために1層パッケージされている.