1. 程式人生 > >Cocos2d-x 2.0.4 小心隱藏的retain

Cocos2d-x 2.0.4 小心隱藏的retain

Cocos2d-x中的CCObject類及其派生類,使用autorelease()方法,將自身交託於CCPoolManager管理器進行管理,都可以使用retain()方法來使自身的引用計數加一,使用release()方法來使自身的引用計數減一,當引用計數為0的時候,CCPoolManager管理器就會將其刪除釋放。

所有例項化Cocos2d-x裡面的以CCObject為基類的類時,都要使用其create()方法來建立物件,對於自己新增的派生類,需要通過 CREATE_FUNC 巨集來實現create()方法,下面以《 如何製作一個橫版格鬥過關遊戲 Cocos2d-x 2.0.4
》來舉例介紹:
Hero.h
1
2
3
4
5
6
7
8
9
class Hero :  public ActionSprite
{
public:
    Hero( void
);
    ~Hero( void);

    CREATE_FUNC(Hero);
     //……
};
若是需要create()方法帶有引數的話,仿造CREATE_FUNC的定義來實現, CREATE_FUNC 巨集定義如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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; \
    } \
}
具體可以類似如下:
SimpleDPad.h
1
2
3
4
5
6
7
8
9
10
class SimpleDPad :  public cocos2d::CCSprite,  public cocos2d::CCTargetedTouchDelegate
{
public:
    SimpleDPad( void);
    ~SimpleDPad( void);

     static SimpleDPad* dPadWithFile(cocos2d::CCString *fileName,  float radius);
     bool initWithFile(cocos2d::CCString *filename,  float radius);
     //……
};
其中dPadWithFile靜態方法就是仿造的create()方法,具體實現如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SimpleDPad* SimpleDPad::dPadWithFile(CCString *fileName,  float radius)
{
    SimpleDPad *pRet =  new SimpleDPad();
     if (pRet && pRet->initWithFile(fileName, radius))
    {
        pRet->autorelease();
         return pRet;
    }
     else
    {
         delete pRet;
        pRet =  NULL;
         return  NULL;
    }
}
當然這裡的方法名可以改為以create開頭方便統一。
變數
當create出來的變數,被 addChild 到以CCNode為基類的類時,或者被 addObject 到CCArray、CCSet等時,都會自動將這個變數物件retain()一次,以防止被自動釋放導致的野指標問題,所以一般情況都不需要再手動呼叫retain()方法了。對於類定義中用 CC_SYNTHESIZE_RETAIN 巨集宣告的變數,或者對臨時變數手動呼叫了retain()方法,一般都需要在解構函式或者特定的函式進行手動呼叫release()方法,類似如下:
GameLayer.h
1
2
3
4
5
6
7
8
9
10
class GameLayer :  public cocos2d::CCLayer,  public SimpleDPadDelegate
{
public:
    GameLayer( void);
    ~GameLayer( void);

    CREATE_FUNC(GameLayer);
     //……
    CC_SYNTHESIZE_RETAIN(cocos2d::CCArray*, _robots, Robots);
};
GameLayer.cpp
1
2
3
4
5
6
7
8
9
10
11
GameLayer::GameLayer( void)
{
     //……
    _robots =  NULL;
}

GameLayer::~GameLayer( void)
{
     //……
    CC_SAFE_RELEASE_NULL(_robots);
}
但是有一種特殊情況,類與變數的互相retain(),導致無法釋放,記憶體洩露。
ActionSprite.h
1
2
3
4
5
6
7
8
9
class ActionSprite :  public cocos2d::CCSprite
{
public:
    ActionSprite( void);
    ~ActionSprite( void);

     //……
    CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _attackAction, AttackAction);
};
Hero.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
bool Hero::init()
{
     bool bRet =  false;
     do 
    {
         //……
         this->setAttackAction(CCSequence::create(CCAnimate::create(attackAnimation), CCCallFunc::create( this, callfunc_selector(Hero::idle)),  NULL));

         //……
    }  while ( 0);

     return bRet;
}   
_attackAction變數以CCSequence類建立,CCSequence建立包含CCCallFunc的建立, CCCallFunc 建立的時候將this指標傳遞下去,跟蹤原始碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
CCCallFunc * CCCallFunc::create(CCObject* pSelectorTarget, SEL_CallFunc selector) 
{
    CCCallFunc *pRet =  new CCCallFunc();

     if (pRet && pRet->initWithTarget(pSelectorTarget)) {
        pRet->m_pCallFunc = selector;
        pRet->autorelease();
         return pRet;
    }

    CC_SAFE_DELETE(pRet);
     return  NULL;
}

bool CCCallFunc::initWithTarget(CCObject* pSelectorTarget) {
     if (pSelectorTarget) 
    {
        pSelectorTarget->retain();
    }

     if (m_pSelectorTarget) 
    {
        m_pSelectorTarget->release();
    }

    m_pSelectorTarget = pSelectorTarget;
     return  true;
}
可以看到它對this指標的物件進行retain()呼叫,導致Hero物件無法被自動釋放,需要先手動對_attackAction變數進行release()呼叫,CCCallFunc將進行析構,進而將會對this指標的物件進行release()呼叫
1
2
3
4
virtual ~CCCallFunc()
{
    CC_SAFE_RELEASE(m_pSelectorTarget);
}
解決方法舉例如下:
ActionSprite.h
1
2
3
4
5
6
class ActionSprite :  public cocos2d::CCSprite
{
public:
     //……
   virtual  void cleanup();
};  
ActionSprite.cpp
1
2
3
4
5
6
7
8
void ActionSprite::cleanup()
{
    CC_SAFE_RELEASE_NULL(_idleAction);
    CC_SAFE_RELEASE_NULL(_attackAction);
    CC_SAFE_RELEASE_NULL(_walkAction);
    CC_SAFE_RELEASE_NULL(_hurtAction);
    CC_SAFE_RELEASE_NULL(_knockedOutAction);

    CCSprite::cleanup();
}   
最後用Visual Leak Detector和DevPartner檢測,均未檢測到記憶體洩露。

如文章存在錯誤之處,歡迎指出,以便改正。By 無幻