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對象:
(小若:很正常啊,有什麼特别的?)
正你妹紙啊,正!你才正!(小若:不要這麼光明正大地贊我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表面上的知識了,至于retain源碼級别的解說,請到紅孩兒的部落格吧,強烈推薦~
好了,不唠叨了~困喇,睡大覺去~~