retain和release倒底怎麼玩?

1. 爲什麼會有retain

C++Java不一樣,Java有一套很方便的垃圾回收機制,當我們不需要使用某個對象時,給它賦予null值即可。而C++new了一個對象之後,不使用的時候通常需要delete掉。

於是,Cocos2d-x就發明了一套內存管理機制(小若:發你妹紙。。。),其實紅孩兒的博客很詳細地解釋了Cocos2d-x的內存管理機制,我沒有能力也不想重複解釋。(小若:那你還寫?= =

Retain的意思是保持引用,也就是說,如果想保持某個對象的引用,避免它被Cocos2d-x釋放,那就要調用對象的retain函數。(小若:爲什麼不retain就會被釋放?)

 

 

2. 真正的兇手autoRelease

既然旁白誠心誠意地問我,那我就光明正大地回答吧(小若:我今天沒力氣吐槽,好吧= =

一旦調用對象的autoRelease函數,那麼這個對象就被Cocos2d-x的內存管理機制給盯上了,如果這個對象沒人認領,那就等着被釋放吧。(小若:= =太久沒吐槽,一時不知道吐什麼好)

 

3. 看代碼實際點

說了這麼多,還是上代碼吧。

創建一個Cocox2d-x的項目,就直接拿HelloWorldScene開刀,修改init函數,在最後添加一句代碼:

bool HelloWorld::init()
{
    bool bRet = false;
    do 
    {
        /* 很多代碼被省略了。。。。。。 */

		testSprite = CCSprite::create("HelloWorld.png");

        bRet = true;
    } while (0);

    return bRet;
}


 

(小若:testSprite是什麼東東?)

testSprite是一個成員變量,在頭文件里加上就可以了:

class HelloWorld : public cocos2d::CCLayer
{
public:
    virtual bool init();  
    static cocos2d::CCScene* scene();
    void menuCloseCallback(CCObject* pSender);
    CREATE_FUNC(HelloWorld);
private:
	cocos2d::CCSprite* testSprite;
};


 

然後,最關鍵的來了,我們修改menuCloseCallback函數:

void HelloWorld::menuCloseCallback(CCObject* pSender)
{
    testSprite->getPosition();
}


 

現在,運行項目,點擊按鈕,看看是什麼情況?

(小若:報錯了!)

如果大家知道怎麼調試項目的話,我們在menuCloseCallback函數裏斷點,用調試模式運行項目,看看testSprite對象:

(小若:很正常啊,有什麼特別的?)

正你妹紙啊,正!你才正!(小若:不要這麼光明正大地讚我O O!)

 

 

我們應該能看到不少非正常數據,圖中已經用紅色圈圈標出來了,這代表testSprite對象被釋放了,現在testSprite指向未知的位置。

這是很危險的,有時候它不會立即報錯,但是在某個時刻突然崩潰!

 

要想解決這個問題,很簡單,再次修改init函數:

bool HelloWorld::init()
{
    bool bRet = false;
    do 
    {
        /* 很多代碼被省略了。。。。。。 */

		testSprite = CCSprite::create("HelloWorld.png");
  testSprite->retain();
  
        bRet = true;
    } while (0);

    return bRet;
}


 

再次運行項目,看看還會不會報錯?(小若:不會了,爲什麼?)

再次用調試模式運行項目,看看testSprite對象:

 

(小若:不正常!都是0!!)

零你妹紙= =(小若:爲什麼今天你總是搶我的對白O O!)

這次我們看到testSprite的數據明顯正常了。

 

 

4. 原理來了

好了,嘮叨了一大堆,還沒有進入正題。

首先,要想讓對象參與內存管理機制,必須繼承CCObject類(CCNodeCCLayer等都繼承了CCObject類)。

然後,調用對象的autoRelease函數,對象就會被Cocos2d-x的內存管理機制盯上,在遊戲的每一幀,內存管理機制都會掃描一遍被盯上的對象,一旦發現對象無人認領,就會將對象殺死!(小若:嗷~殘忍!)

如果不想讓對象被殺死,那麼就要調用對象的retain函數,這樣對象就被認領了,一旦對象被認領,就永遠不會被內存管理機制殺掉,是永遠,一輩子。(小若:好朋友,一輩子= =

但,對象一輩子都不被釋放的話,那麼就會產生內存泄露,你試試加載一個佔20M內存的對象一輩子不釋放,不折騰死纔怪~(小若:你去加載一個20M的對象本身就是閒的那個什麼疼啊!)因此,當你不需要再使用這個對象時,就要調用對象的release函數,這是和retain對應的。一般可以在析構函數裏調用release函數。

 

 

5. 實際情況

講道理,大家都懂,但是,相信很多朋友在實際寫代碼的時候,還是會感覺很混亂。

比如,什麼時候該retain?大家是不是發現,有時候不retain也不會報錯?

其實這很簡單,因爲我們經常會在create一個對象之後,添加到層裏,如:

testSprite = CCSprite::create("HelloWorld.png");

this->addChild(testSprite);

addChild函數就是導致大家混亂的兇手了,addChild函數會調用對象的retain函數,爲什麼它要調用對象的retain函數呢?因爲你都把對象送給它當孩子了,它當然要認領這個對象了!(小若:我懂了,嗷!)

於是,當我們把對象addChildCCLayer時(不一定是CCLayerCCArrayCCNode都行),我們就不需要調用對象的retain函數了。

 

 

6. 那倒底什麼時候要retain

說了這麼多,還是沒有說清楚,什麼時候要調用對象的retain

很簡單,當你把一個對象作爲成員變量時,並且沒有把對象addChild到另外一個對象時,就需要調用retain函數。

 

7. 最後的最後

一定要記住,必須要調用了對象的autoRelease函數之後,retainrelease函數纔會生效,否則,一切都是徒勞。

因此,十分建議使用create的方式創建對象,如:

CCSprite* CCSprite::create(const char *pszFileName)
{
    CCSprite *pobSprite = new CCSprite();
    if (pobSprite && pobSprite->initWithFile(pszFileName))
    {
        pobSprite->autorelease();
        return pobSprite;
    }
    CC_SAFE_DELETE(pobSprite);
    return NULL;
}


 

這些就是retain表面上的知識

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章