Cocos 2 Dxのメモリ管理

10390 ワード

Cocos 2 Dxのすべてのクラスは直接または間接的にCCObjectから継承され、クラスに自動的なメモリ管理能力を持たせる.根本的には、アップルのデザイン思考を参考にしています.iPhone版のCocos 2 DxからC++版のCocos 2 Dx、および後のJS、HTML 5まで、APIの一貫性を保つことで、開発者が迅速に適応したり、新しい開発言語を選択したりするのに役立ちます.踏襲するObjective-Cスタイルは個人的にはいいと思いますが、C++開発を採用するには自分のスタイルが必要です.あるいは規範と呼ばれ、開発者を指導し、C++のジャングルに迷わないようにします.
Cocos 2 Dxのメモリ管理には2つの考え方があります.1つは参照カウント、2つは自動回収プールです.参照数は、オブジェクトが参照されているかどうかを記録し、参照されていない場合はオブジェクトを解放します.自動回収プールは、オブジェクトの参照を解放して管理されます.
class CC_DLL CCObject : public CCCopying
{
public:
    unsigned int m_uID;
protected:
    unsigned int m_uReference;
    unsigned int m_uAutoReleaseCount;
public:
    CCObject(void);
    virtual ~CCObject(void);
    void release(void);
    void retain(void);
    CCObject* autorelease(void);
    bool isSingleReference(void) const;
    unsigned int retainCount(void) const;
    friend class CCAutoreleasePool;
};

CCObjectのreleaseとretainは参照カウント機能を実現する.Autoreleaseはリサイクルプール機能を実現します.簡単にするために、CCObjectの他の部分とスクリプトに関連する部分を捨てます.C++のオブジェクト構造はコンストラクション関数から始まり,まずコンストラクション関数を見てみる.
CCObject::CCObject(void)
: m_uReference(1) 
, m_uAutoReleaseCount(0)
{
    static unsigned int uObjectCount = 0;
    m_uID = ++uObjectCount;
}

オブジェクトが作成されると、その参照カウント(m_uReference)は1に初期化されます.自動回収プールには初期化されていないので、自動参照カウント(m_uAutoReleaseCount)は0です.興味深いことに、CCObjectにはもう一人の共通メンバーがいますm_uIDは、オブジェクトのグローバル一意のIDです.グローバルIDがマルチスレッド環境で心配されているかもしれませんが、今は主に単一スレッドを使用しています.
オブジェクトが作成されましたが、いつリリースされますか?
ここでは,Cocos 2 DxがObject−Cから参照したオブジェクトの初期化と解放方式を説明する必要がある.C++の一般的な方法は、new/new[]を先に、delete/delete[]を次にします.Cocos 2 Dxのオブジェクト構造は一般的にnewを直接呼び出すのではなく,クラスの静的関数create()を介して内部的にnewを除去する.
一つの自分.オブジェクトの初期化は、コンストラクション関数に直接依存するのではなく、init関数を使用します.
CCSpriteのcreate関数を見てみましょう.
CCSprite* CCSprite::create()
{
    CCSprite *pSprite = new CCSprite();
    if (pSprite && pSprite->init())
    {
        pSprite->autorelease();
        return pSprite;
    }
    CC_SAFE_DELETE(pSprite);
    return NULL;
}
#define CC_SAFE_DELETE(p) do { if(p) { delete (p); (p) = 0; } } while(0)

Cocos 2 Dxのnewオブジェクトは、メモリ領域を申請し、アクセスカウントを初期化しただけです.オブジェクト自体の初期化は、オブジェクトが提供するinitを呼び出して完了する必要があります.newがCCSpriteを出してinit()を呼び出してオブジェクト初期化を行った後、CCObjectのautorelease()を呼び出して自動回収プールに入れます.
CCObject* CCObject::autorelease(void)
{
    CCPoolManager::sharedPoolManager()->addObject(this);
    return this;
}

CCPoolManagement::sharedPoolManager()は、オブジェクトプールマネージャの一例を返します.単例モードの使用はCocos 2 Dxでも多い.本質的に単一の例はグローバル変数であり、パッケージされたエントリを提供するだけです.CCPoolManagerは、スタック型のオブジェクトプールを維持します.
void CCPoolManager::addObject(CCObject* pObject)
{
    getCurReleasePool()->addObject(pObject);
}
CCAutoreleasePool* CCPoolManager::getCurReleasePool()
{
    if(!m_pCurReleasePool)
    {
        push();
    }
    return m_pCurReleasePool;
}
void CCPoolManager::push()
{
    CCAutoreleasePool* pPool = new CCAutoreleasePool(); //CCAutoreleasePool CCObject      1
    m_pCurReleasePool = pPool;
    m_pReleasePoolStack->addObject(pPool); //    pPool retain(), CCObject           2
    pPool->release(); //CCAutoreleasePool CCObject      1。      CCAutoreleasePool         ,              ,           
}

CCPoolManagerのpush()とpop()は、オブジェクトの自動回収プールスタックのメンテナンスを担当します.CCPoolManager内部では、CCArrayを使用してデータストレージを行います.スタックの上部は配列の後ろにあり(インデックス値が大きい)、スタックの下部は配列の前にあります.autorelease()を呼び出して現在のスタックの上部にあるオブジェクトプールにオブジェクトを追加すると、現在のオブジェクトがプールm_を回収するpCurReleasePoolが空の場合、pushを呼び出してオブジェクト回収プールを作成し、CCPoolManagerに追加します.CCPoolManagerは、現在の自動回収プールを返し、CCObjectのautorelease()にオブジェクトを追加します.
注意push()のコードは、newを使用するため、参照カウントを自分で維持する必要があります.すべてのコンテナのaddObjectメソッドでは、追加されたオブジェクトの参照数が内部的に増加します.これは、現在のコンテナに対してもオブジェクトへの参照があるため理解できます.push()の後にrelease()が呼び出されたのは,pPoolポインタがコードブロックから飛び出した後に無効になったためであり,外部から見るとコンテナのみが一意の参照を持つ.これがオブジェクトの所有権の伝達です.所有権の伝達には特に注意が必要です.そうしないと、参照カウントエラーが発生し、オブジェクトが早期に解放され、解放されなくなります.メモリの管理は追加の負担であり、適切な理由がなければ、自分でnewオブジェクトに行かないで、リファレンスカウントを維持します.煩わしい細部は隠されるべきだ.
void CCAutoreleasePool::addObject(CCObject* pObject)
{
    m_pManagedObjectArray->addObject(pObject);
    ++(pObject->m_uAutoReleaseCount);
    pObject->release(); 
}

CCAutoreleasePoolの内部にも、CCArrayによって管理されているオブジェクトが格納されています.CCArrayのaddObject(pObject)は、オブジェクトpObjectの参照カウントに1を加算するので、release()を呼び出して参照カウントを1に減算します.
void CCArray::addObject(CCObject* object)
{
    ccArrayAppendObjectWithResize(data, object);
}
void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object) 
{ 
    ccArrayEnsureExtraCapacity(arr, 1); 
    ccArrayAppendObject(arr, object); 
}
void ccArrayAppendObject(ccArray *arr, CCObject* object) 
{ 
    object->retain(); 
    arr->arr[arr->num] = object; 
    arr->num++; 
}

release()とretain()の実現を見る時だ.
void CCObject::release(void)
{
    --m_uReference;
    if (m_uReference == 0)
    {
        delete this;
    }
}
void CCObject::retain(void)
{
    ++m_uReference;
}

release()とretain()は、参照カウントを1と1を加算します.release()はオブジェクトを削除し、autorelease()は自動回収プールにオブジェクトを追加します.しかし、どのオブジェクト回収プールがいつ本当に管理されているオブジェクトを解放しますか?オブジェクト回収プールの回収には、個別のスレッドが必要ですか?Cocos 2 Dxは、CCDisplayLinkDirector::mainLoop(void)でCCPoolManagement::sharedPoolManager()->pop()を呼び出して回収プールの解放を完了します.
WIN 32を例に、Cocos 2 Dxのメッセージループがオブジェクト回収プールをどのように処理しているかを見てみましょう.
適用されるメッセージ・ループは、次の2つの階層に分けられます.
  • 1は、CCApplication::run()です.主にオペレーティングシステムに関するメッセージの取得、配布などの機能を完了します.この層はプラットフォームに関連しており、プラットフォームごとに異なる実装があります.
  • は、CCDirector::sharedDirector()->mainLoop()です.これはCocos 2 Dxのメインループであり,ゲーム画面の描画,イベントスケジューリングなどを担当する.この層はCocos 2 Dxのコア動作メカニズムである.このレイヤの前のレイヤはネストされた関係ですが、run内のループはオペレーティングシステムメッセージを1回取得し、mainLoopに応じて1回呼び出されません.mainLoopの呼び出しタイミングはゲームのフレーム間隔によって決定され、フレーム間隔時間に達しなければmainLoopは呼び出されない.

  • 完全な起動プロセスについては、次の記事で説明します.CCDirector::sharedDirector()->mainLoop().
    void CCDisplayLinkDirector::mainLoop(void)
    {
        if (m_bPurgeDirecotorInNextLoop)
        {
            m_bPurgeDirecotorInNextLoop = false;
            purgeDirector();
        }
        else if (! m_bInvalid)
         {
             drawScene();
             CCPoolManager::sharedPoolManager()->pop();        
         }
    }

    mainLoopの前でCCDirectorのend()が呼び出されたかどうかを判断します.つまりゲーム終了です.もしそうであれば、CCDirectorはクリーンアップを行い、そうでなければシーンを描画し、CCPoolManager::pop()を呼び出してメモリプールを解放します.
    void CCPoolManager::pop()
    {
        if (! m_pCurReleasePool)
        {
            return;
        }
        int nCount = m_pReleasePoolStack->count();
        m_pCurReleasePool->clear();
        if(nCount > 1)
        {
            m_pReleasePoolStack->removeObjectAtIndex(nCount-1);
            m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount - 2);
        }
    }

    pop()まず,現在アクティブなオブジェクト回収プールがあるかどうかをチェックし,なければ何もしない.autorelease()を呼び出したり、オブジェクト回収プールにオブジェクトを管理したりしていない場合は、このような状況が発生します.次に、オブジェクト回収プールCCAutoreleasePoolのclear()関数を呼び出します.clear()オブジェクト回収プールを巡り、各オブジェクトのm_をuAutoReleaseCountは1を減らします.そして、これらのオブジェクトは、オブジェクト回収プールの内部格納容器CCArrayから削除される.
    pop()を呼び出し,歳桟頂のオブジェクト回収プールだけを解放した.スタック内の他のオブジェクトプールは依然として存在し、何の変化もありません.ただし、現在のオブジェクト回収プールを調整する必要があります.m_pCurReleasePoolは現在スタックの頂上にある回収プールであるはずです.
    void CCAutoreleasePool::clear()
    {
        if(m_pManagedObjectArray->count() > 0)
        {
            CCObject* pObj = NULL;
            CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)
            {
                if(!pObj)
                    break;
                --(pObj->m_uAutoReleaseCount);
            }
            m_pManagedObjectArray->removeAllObjects();
        }
    }

    CCArray::addObjectはretainを呼び出して参照カウントを増やします.これに対応して、CCArrayのすべての削除インタフェースはオブジェクトのrelease()を呼び出して参照カウントを減らし、参照カウントが0のときにオブジェクトを解放します.
    void CCArray::removeAllObjects()
    {
        ccArrayRemoveAllObjects(data);
    }
    void ccArrayRemoveAllObjects(ccArray *arr)
    {
        while( arr->num > 0 )
        {
            (arr->arr[--arr->num])->release();
        }
    }

    前述の解析から,retain()とrelease()はそれぞれオブジェクトを取得し,オブジェクトを解放するために用いられることが分かった.Autotorelease()は、自動回収プールにオブジェクトを追加するために使用され、オブジェクト回収プールは、フレーム間隔ごとに一度回収プール内のオブジェクトを統一的に解放します.この3つの関数を使用するだけで、開発者はメモリのメンテナンスの代価を持っています.Cocos 2 Dxは、メモリのメンテナンスのコストを簡素化するために、いくつかの符号化ガイドラインをさらに利用しています.
    前述したように、自分がnewを通じてオブジェクトを構築するには、自分で余分な仕事をする必要があります.静的create()を使用してオブジェクトを構築することを推奨します.create()コンストラクションオブジェクトは、newからオブジェクトをエクスポートし、オブジェクトのinit()関数を初期化してオブジェクト回収プールに追加します.Cocos 2 DxはCREATE_を提供していますFNCはcreate()関数を完成させるのに役立ちます.クラスのpublicメンバーにこのようなマクロを挿入するだけで便利です.
    #define CREATE_FUNC(__TYPE__) \
    static __TYPE__* create() \
    { \
        __TYPE__ *pRet = new __TYPE__(); \
        if (pRet && pRet->init()) \
        { \
            pRet->autorelease(); \
            return pRet; \
        } \
        else \
        { \
            delete pRet; \
            pRet = NULL; \
            return NULL; \
        } \
    }

    興味深いことに、Cocos 2 Dxは他にも非常に有用なマクロを提供しています:CC_SYNTHESIZEヘルプSetter/Getter,CCARRAY_の作成FOREACHヘルプ配列、CC_SAFE_DELETEなどのヘルプはオブジェクトを解放し、CC_BREAK_IFは条件判断をする.マクロもたくさんあるので、勉強して参考にすることができます.
    最後に、メモリ管理を終了する例を示します.
    例:
    static CCLayer* backAction()
    {
        sceneIdx--;
        int total = MAX_LAYER;
        if( sceneIdx < 0 )
            sceneIdx += total;
        CCLayer* pLayer = (createFunctions[sceneIdx])();          //    CCLayer,    CREATE_FUNC ,     1,       0
        pLayer->autorelease();        //          ,     1,       1
        return pLayer;
    }
    void LayerTest::backCallback(CCObject* pSender)
    {
        CCScene* s = new LayerTestScene();    //    LayerTestScene  ,     1,       0
        s->addChild( backAction() );            //CCLayer       1,     2,       1
        CCDirector::sharedDirector()->replaceScene(s);    //LayerTestScene       1,     2,       0
        s->release();    //LayerTestScene       1,     1,       0
    }

    CCLayerの参照カウントは2、自動参照カウントは1です.1フレーム間隔が経過すると、CCAutoreleasePool::pop()が呼び出され、さらにCCAutoreleasePool::clear()が呼び出され、最終的にCCArray::removeAllObjects()が呼び出され、すべてのCCArray内のオブジェクトに対してrelease()が1回呼び出されます.CCLayerの参照カウントは1、自動参照カウントは0です.LayerTestScene継承構造にはCCNodeがあり、その構造関数はすべての子供ノードを解放します.
    CC_SAFE_RELEASE(m_pChildren)
        ->CCArray.release()
            ->ccArrayFree(data)
                ->ccArrayRemoveAllObjects(arr)
                    ->CCLayer.release()
    CCLayer現在の参照カウントは1、自動参照カウントは0であり、release()を呼び出すとCCLayerが解放されます.親ノードLayerTestSceneの解放に依存するCCLayerの解放が見られる.
    LayerTestSceneの参照カウントは1、自動参照カウントは0です.新しいSceneが表示されると、Sceneの切り替えが行われ、古いSceneがCC_SAFE_RELEASE削除(release()を呼び出します).