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類(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表面上的知識