天天看點

【木頭Cocos2d-x 037】retain和release倒底怎麼玩?

retain和release倒底怎麼玩?

呼呼,好久沒有釋出教程了(小若:難得清靜了,你為毛又出來吓人= =),其實最近木頭我在準備出版書籍的事情。但是貌似不太順利,果然我還是積累不夠,寫書的過程壓力好大,感覺寫不出有趣的文字出來(小若:嗷、、、)。果然還是在部落格寫自由一些?嘿嘿~

最近以及最不是很近(小若:書裡一定不能出現這些錯誤的語句,是以你才寫不出來吧= =),不少朋友對retain的認識似乎有點模糊,今天我就和大家分享一下關于retain的知識吧~

笨木頭花心貢獻,啥?花心?不呢,是用心~

轉載請注明,原文位址: http://blog.csdn.net/musicvs/article/details/8689345

正文:

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對象:

【木頭Cocos2d-x 037】retain和release倒底怎麼玩?

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

正你妹紙啊,正!你才正!(小若:不要這麼光明正大地贊我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對象:

【木頭Cocos2d-x 037】retain和release倒底怎麼玩?

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

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

這次我們看到testSprite的資料明顯正常了。

4. 原理來了

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

首先,要想讓對象參與記憶體管理機制,必須繼承CCObject類(CCNode、CCLayer等都繼承了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函數呢?因為你都把對象送給它當孩子了,它當然要認領這個對象了!(小若:我懂了,嗷!)

于是,當我們把對象addChild到CCLayer時(不一定是CCLayer,CCArray、CCNode都行),我們就不需要調用對象的retain函數了。

6. 那倒底什麼時候要retain?

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

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

7. 最後的最後

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

是以,十分建議使用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表面上的知識了,至于retain源碼級别的解說,請到紅孩兒的部落格吧,強烈推薦~

好了,不唠叨了~困喇,睡大覺去~~

繼續閱讀