引用計數
引用計數是c/c++項目中一種古老的內存管理方式。當我8年前在研究一款名叫TCPMP的開源項目的時候,引用計數就已經有了。
iOS SDK把這項計數封裝到了NSAutoreleasePool中。所以我們也在cocos2d-x中克隆了一套CCAutoreleasePool。兩者的用法基本上一樣,所以假如你沒有涉及過ios開發,你可以看看蘋果官方文檔NSAutoreleasePool Class Reference。
CCAutoreleasePool
Cocos2d-x的CCAutoreleasePool和cocoa的NSAutoreleasePool有相同的概念和API,但是有兩點比較重要的不同:
1.CCAutoreleasePool不能被開發者自己創建。Cocos2d-x會為我們每一個游戲創建一個自動釋放池實例對象,游戲開發者不能新建自動釋放池,僅僅需要專注于release/retain cocos2d::CCObject的對象。
2.CCAutoreleasePool不能被用在多線程中,所以假如你游戲需要網絡線程,請僅僅在網絡線程中接收數據,改變狀態標志,不要這個線程里面調用cocos2d接口。下面就是原因:
CCAutoreleasePool的邏輯是,當你調用object->autorelease(),object就被放到自動釋放池中。自動釋放池能夠幫助你保持這個object的生命周期,直到當前消息循環的結束。在這個消息循環的最后,假如這個object沒有被其他類或容器retain過,那么它將自動釋放掉。例如,layer->addChild(sprite),這個sprite增加到這個layer的子節點列表中,他的聲明周期就會持續到這個layer釋放的時候,而不會在當前消息循環的最后被釋放掉。
這就是為什么你不能在網絡線層中管理CCObject生命周期,因為在每一個UI線程的最后 ,自動釋放對象將會被刪除,所以當你調用這些被刪掉的對象的時候,你就會遇到crash。
CCObject::release(), retain() and autorelease()
簡而言之,這只有兩種情況你需要調用release()方法
1. 你new一個cocos2d::CCObject子類的對象,例如CCSprite,CCLayer等。
2. 你得到cocos2d::CCObject子類對象的指針,然后在你的代碼中調用過retain方法。
下面例子就是不需要調用retain和release方法:
CCSprite* sprite = CCSprite::create("player.png");
這里就沒有更多的代碼用于sprite了。但是請注意sripte->autorelease()已經在CCSprite::create(const char*)方法中被調用了,因此這個sprite將在消息循環的最后自動釋放掉。
使用靜態構造函數
CCSprite::create("player.png")是一個使用靜態構造函數的例子。所以在cocos2d-x中所有的類,除了單例,都提供了靜態構造函數,這些靜態構造函數包含下面4項操作:
1. 新建一個對象
2. 調用object->init(...)
3. 假如初始化成功,例如,成功的找到紋理文件,那么接下來將會調用object->autorelease()。
4. 返回這個已經被標記了autorelease的對象。
所有CCAsdf::createWithXxxx(...)這種類型的函數都有以上這些方式。
在cocos2d-x v1.x或者更早版本里,這些方式是:
CCSprite* sprite = CCSprite::spriteWithTexture(...)
使用這些靜態構造函數,你不需要關心“new”, “delete”和“autorelease”,僅僅關心object->retain() 和 object->release()。
一個錯誤的例子
一個開發者報告了一個使用CCArray 并導致crash的例子
bool HelloWorld::init()
{
bool bRet = false;
do
{
//////////////////////////////////////////////////////////////////////////
// super init first
//////////////////////////////////////////////////////////////////////////
CC_BREAK_IF(! CCLayer::init());
//////////////////////////////////////////////////////////////////////////
// add your codes below...
//////////////////////////////////////////////////////////////////////////
CCSprite* bomb1 = CCSprite::create("CloseNormal.png");
CCSprite* bomb2 = CCSprite::create("CloseNormal.png");
CCSprite* bomb3 = CCSprite::create("CloseNormal.png");
CCSprite* bomb4 = CCSprite::create("CloseNormal.png");
CCSprite* bomb5 = CCSprite::create("CloseNormal.png");
CCSprite* bomb6 = CCSprite::create("CloseNormal.png");
addChild(bomb1,1);
addChild(bomb2,1);
addChild(bomb3,1);
addChild(bomb4,1);
addChild(bomb5,1);
addChild(bomb6,1);
m_pBombsDisplayed = CCArray::create(bomb1,bomb2,bomb3,bomb4,bomb5,bomb6,NULL);
//m_pBombsDisplayed 是在頭文件中被定義為一個 protected 變量.
// <--- 我們應該添加在這里m_pBombsDisplayed->retain()方法來防止在HelloWorld::refreshData()中crash。
this->scheduleUpdate();
bRet = true;
} while (0);
return bRet;
}
void HelloWorld::update(ccTime dt)
{
refreshData();
}
void HelloWorld::refreshData()
{
m_pBombsDisplayed->objectAtIndex(0)->setPosition(cpp(100,100));
}
他的錯誤是m_pBombsDisplayed是使用CCArray::create(...)創建的,這種創建方式是靜態構造方式,這個數組被標記了autorelease。
所以這個數組會在當前消息循環的最后被CCAutoreleasePool釋放掉。
當后面的消息循環調用HelloWorld::update(ccTime)的時候,m_pBombsDisplayed已經是一個野指針了,這就將引起崩潰。
為了修復這個崩潰情況,我們需要增加m_pBombsDisplayed->retain()在 m_pBombsDisplayed =CCArray::create(...);之后,
并且在 HelloWorld::~HelloWorld() 的析構函數中調用m_pBombsDisplayed->release()。