Cocos2dx 入門內存引用計數器

引用計數

引用計數是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()。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1.OC里用到集合類是什么? 基本類型為:NSArray,NSSet以及NSDictionary 可變類型為:NS...
    輕皺眉頭淺憂思閱讀 1,394評論 0 3
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,210評論 30 471
  • v3.0 亮點 @來自官網 使用 C++(C++11) 的特性取代了 Objective-C 的特性優化了 Lab...
    光明程輝閱讀 1,065評論 0 1
  • 內存管理 簡述OC中內存管理機制。與retain配對使用的方法是dealloc還是release,為什么?需要與a...
    丶逐漸閱讀 2,008評論 1 16
  • 求助時站在對方立場 道謝時加上具體原因 搭建人脈是真誠互助 準備禮物以表心意保持友誼溫度
    會飛的蓉子閱讀 178評論 0 0