Cocos2dx-記憶體管理機制(autorelease、release、retain)

君墨痕發表於2014-03-14

原文出自 : 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函式,在最後新增一句程式碼:

  1. bool HelloWorld::init()  
  2. {  
  3.     bool bRet = false;  
  4.     do   
  5.     {  
  6.         /* 很多程式碼被省略了。。。。。。 */  
  7.   
  8.         testSprite = CCSprite::create("HelloWorld.png");  
  9.   
  10.         bRet = true;  
  11.     } while (0);  
  12.   
  13.     return bRet;  
  14. }  


 

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

testSprite是一個成員變數,在標頭檔案里加上就可以了:

  1. class HelloWorld : public cocos2d::CCLayer  
  2. {  
  3. public:  
  4.     virtual bool init();    
  5.     static cocos2d::CCScene* scene();  
  6.     void menuCloseCallback(CCObject* pSender);  
  7.     CREATE_FUNC(HelloWorld);  
  8. private:  
  9.     cocos2d::CCSprite* testSprite;  
  10. };  


 

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

  1. void HelloWorld::menuCloseCallback(CCObject* pSender)  
  2. {  
  3.     testSprite->getPosition();  
  4. }  


 

現在,執行專案,點選按鈕,看看是什麼情況?

(小若:報錯了!)

如果大家知道怎麼除錯專案的話,我們在menuCloseCallback函式裡斷點,用除錯模式執行專案,看看testSprite物件:

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

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

 

 

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

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

 

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

  1. bool HelloWorld::init()  
  2. {  
  3.     bool bRet = false;  
  4.     do   
  5.     {  
  6.         /* 很多程式碼被省略了。。。。。。 */  
  7.   
  8.         testSprite = CCSprite::create("HelloWorld.png");  
  9.   testSprite->retain();  
  10.     
  11.         bRet = true;  
  12.     } while (0);  
  13.   
  14.     return bRet;  
  15. }  


 

再次執行專案,看看還會不會報錯?(小若:不會了,為什麼?)

再次用除錯模式執行專案,看看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的方式建立物件,如:

  1. CCSprite* CCSprite::create(const char *pszFileName)  
  2. {  
  3.     CCSprite *pobSprite = new CCSprite();  
  4.     if (pobSprite && pobSprite->initWithFile(pszFileName))  
  5.     {  
  6.         pobSprite->autorelease();  
  7.         return pobSprite;  
  8.     }  
  9.     CC_SAFE_DELETE(pobSprite);  
  10.     return NULL;  
  11. }  


 

這些就是retain表面上的知識了,至於retain原始碼級別的解說,請到紅孩兒的部落格吧,強烈推薦~

 

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


相關文章