天天看點

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 無幻