cocos2d-x 從onEnter、onExit、 引用計數 談內存泄露問題

///////////////////////////////////
//author : zhxfl
//date   : 2013.8.29
//email  : 291221622@qq.com
///////////////////////////////////
 
在看這個以前,你先要了解onEnter , onExit 和 構造函數,析構函數在調用順序上面的區別
總的來講,順序以下
 
構造函數{}
onEnter{}
onExit{}
析構函數{}
 
講這個以前須要講一些在實踐中遇到的問題。前段時間在項目中fix掉兩個和內存相關的bug,而後就對這兩個方法存在有一些新的理解。
 
1 咱們用的cocos2d-x的版本是cocos2d-1.0.1-x-0.12.0,那時候尚未CCListView.cpp這個列表控件,因此項目組就本身寫了這個控件。咱們知道列表控件的須要放圖片或者文字等等信息,因此必須寫成虛類。以下
 
struct IListItem:
{
    //how we draw this item
    virtual cocos2d::CCNode *createNode(float width) = 0;
    virtual ~IListItem() {}
};
View Code
 
對應的Item須要繼承IListView才能來編寫這個接口,以下例子
 
class FightItem : public IListItem
{
public:

    FightItem(const DailyProperty &dp);
    ~FightItem();
    virtual cocos2d::CCNode *createNode(float width);
    virtual void onExit();
    void selected();
    void unselected();

    static FightItem *itemWithFight(const DailyProperty &dp);
    const DailyProperty &getDailyProperty()
    {
        return m_dp;
    }

private:
    cocos2d::CCNode *m_node;
};
View Code
 
看完這個接口,若是咱們申明瞭FightItem *item(下面都是用item來指FightItem實例),那麼item的釋放咱們只能使用delete了。原本用delete也沒什麼關係,隨着需求的變化,咱們須要用到一些高級的方法,例如在FigthItem裏面。這時候咱們就須要調用CCCallFunc::actionWithTarget(this,callfunc_selector(FigthItem::change) 相似於這樣的方法。這裏就要求FightItem繼承CCObject才能使用callfunc_selector。因此FightItem的聲明被改爲了
 
class FightItem : public IListItem, cocos2d::CCObject
{
public:

    FightItem(const DailyProperty &dp);
    ~FightItem();
    virtual cocos2d::CCNode *createNode(float width);
    virtual void onExit();
    void selected();
    void unselected();

    static FightItem *itemWithFight(const DailyProperty &dp);
    const DailyProperty &getDailyProperty()
    {
        return m_dp;
    }

private:
    cocos2d::CCNode *m_node;
    void change();
};
View Code
 
到這裏,內存管理就開始出現問題了,就是release和delete的混用的問題,FightItem如今是由引用計數管理着,而CCListView裏面就用delete把FightItem釋放了,而後引用計數仍然認爲FightItem還沒釋放,這裏就是double free,釋放兩次的錯誤了。
 
到這裏,須要總結一句,框架引入了引用計數作內存管理的時候,儘可能不用混用delete,不然其餘人不知道混用了,就會出這種問題。

很天然的,CCListView裏面也採用 FightItem * item; item->release(),這樣的釋放機制,問題就決絕了。接着,下一波問題出現了。
再次出現的是泄露問題了。
 
咱們須要在FightItem 裏面播放一個動畫,這時咱們有個地方就用了以下的方法
 
CCScheduler::sharedScheduler()->scheduleSelector(
          schedule_selector(FightItem::showTimeDescription), this, 0.5f, false);
View Code
 
好的,如今咱們的析構函數這樣寫
 
FightItem::~FightItem()
{
    CCScheduler::sharedScheduler()->unscheduleSelector(
        schedule_selector(FightItem::showTimeDescription), this);
    if(m_node) m_node->release();
}
View Code
 
這樣看不出來有什麼問題是吧,不過在偶然的狀況下,我發現這個析構函數根本不執行。真的很偶然,這樣的泄露確實是很難找出來的,因此先創建起這個意識確實很重要,等到項目代碼多了出這種問題,就要整個項目排查了。下面分析一下爲何不執行。
 
FightItem建立的時候count[引用計數]爲1, 執行
CCScheduler::sharedScheduler()->scheduleSelector(
          schedule_selector(FightItem::showTimeDescription), this, 0.5f, false);
的時候,引用計數爲2,加入CCListView引用計數爲3(注意當引用計數爲1的時候,會被系統直接釋放掉)。說明CCScheduler佔用的了FightItem。按照原來的刪除過程,CCListView執行item->release(),FightItem引用計數變爲2,還被CCScheduler佔用。按照C++的機制,delete沒有執行,~FightItem也沒有進入因此
CCScheduler::sharedScheduler()->unscheduleSelector(
schedule_selector(FightItem::showTimeDescription), this);
函數雖然寫在裏面了,但是沒有執行的機會,這樣item永遠沒有釋放的機會。聽起來像不像數據庫裏面的死鎖啊。
 
終於能夠開始寫主題了,這時候咱們就須要虛函數onExit了,分寫修改以下:
struct IListItem:public cocos2d::CCObject
{
    //how we draw this item
    virtual cocos2d::CCNode *createNode(float width) = 0;
    virtual ~IListItem() {}
    virtual void onExit() {}
};

FightItem::~FightItem()
{
    if(m_node) m_node->release();
}

void FightItem::onExit()
{
    CCScheduler::sharedScheduler()->unscheduleSelector(
        schedule_selector(FightItem::showTimeDescription), this);
}
View Code
這時候CCListView執行item->release()改爲這樣
item->onExit()
item->release();
onExit()把CCScheduler的引用清掉,接着CCListView執行release(),這時候引用計數爲0,系統就會執行delete了,這時候~FightItem()就會進入了。
 
這時候就能夠得出最後結論了,onEnter,onExit是配合引用計數機制存在的,總的來講,全部形成本對象被其餘對象引用的操做,都放在onEnter裏面,全部去掉本對象被其餘對象引用的操做都放在onExit裏面,這樣就能保證本對象在釋放的時候可以成功了。
 
因此,咱們通常都是寫成以下的規範,就不多出現內存泄露問題了
void CCImageBtn::onEnter()
{
    CCNode::onEnter();
    CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, m_touchPriority, true);
}

void CCImageBtn::onExit()
{
    CCTouchDispatcher::sharedDispatcher()->removeDelegate(this);
    CCNode::onExit();
}
View Code
相關文章
相關標籤/搜索