Cocos2d x的記憶體管理

2022-07-15 20:21:15 字數 4939 閱讀 8453

要是完全沒有接觸過objc, 只是了解c++, 看到cocos2d-x的記憶體管理設計, 會想說髒話的. 了解objc的話, 起碼還能理解cocos2d-x的開發者是嘗試在c++中模擬objc的記憶體管理方式. 不僅僅是說加引用計數而已, 因為真要在c++中加引用計數的方法有很多種, cocos2d-x用的這種方法, 實在太不原生態了.

因為cocos2d-x中牽涉到顯示的情況最多, 我也就不拿ccarray這種東西做例子了, 看個ccsprite的例子吧, 用cocos2d-x的xcode template生成的helloworld工程中, 刪除原來的顯示**, 建立乙個sprite並顯示的**如下:

// create a scene. it's an autorelease object

ccscene

*scene

=helloworld

::scene

();ccsprite

*helloworld

=new

ccsprite;if

(helloworld

->

initwithfile

("helloworld.png"

))這裡暫時不管helloworld::scene, 先關注ccsprite的建立和使用, 這裡使用new建立了ccsprite, 然後使用scene的addchild函式, 新增的到了scene中, 並顯示. 一段這樣簡單的**, 但是背後的東西卻很多, 比如, 為啥我在scene的addchild後, 呼叫了sprite的release函式呢?

還是可以從引用計數的所有權上說起(這樣比較好理解, 雖然你也可以死記哪些時候具體引用計數的次數是幾). 當我們用new建立了乙個sprite時, 此時sprite的引用計數為1, 並且所有權屬於helloworld這個指標, 我們在把helloworld用scene的addchild函式新增到scene中後, helloworld的引用計數此時為2, 由helloworld指標和scene共享所有權, 此時, helloworld指標的作用其實已經完了, 我們接下來也不準備使用這個指標, 所有權留著就再也釋放不了了, 所以我們用release方法特別釋放掉helloworld指標此時的所有權, 這麼呼叫以後, 最後helloworld這個sprite所有權完全的屬於scene.

但是我們這麼做有什麼好處呢? 好處就是當scene不想要顯示helloworld時, 直接removechild helloworld就可以了, 此時沒有物件再擁有helloworld這個sprite, 引用技術為零, 這個sprite會如期的釋放掉, 不會導致記憶體洩漏.

比如說下列**:

// create a scene. it's an autorelease object

ccscene

*scene

=helloworld

::scene

();// ccsprite* sprite = ccsprite::create("helloworld.png");

ccsprite

*helloworld

=new

ccsprite;if

(helloworld

->

initwithfile

("helloworld.png"

))

上面的**helloworld sprite能正常的析構和釋放記憶體, 假如少了那句release的**就不行.

這個部分是引用計數方法都會碰到的問題, 也就是引用計數到底在什麼時候增加, 什麼時候減少.

在cocos2d-x中, 我倒是較少會像在objc中手動的retain物件了, 主要的物件主要由ccnode和ccarray等容器管理. 在cocos2d-x中, 以cc開頭的, 模擬objc介面的容器, 都是對引用計數有影響的, 而原生的c++容器, 對cocos2d-x的物件的引用計數都沒有影響, 這導致了人們使用方式上的割裂. 大部分用慣了c++的人, 估計都還是偏向使用c++的原生容器, 畢竟c++的原生容器及其配套演算法算是c++目前為數不多的亮點了, 比objc原生的容器都要好用, 更別說cocos2d-x在c++中模擬的那些objc容器了. 但是, 一旦走上這條路就需要非常小心, 要非常明確此時每個物件的所有權是誰.

看下面的**:

vector

<

ccsprite

*>

sprites

;for

(inti=

0;i<3;

++i)}

因為c++的容器是對cocos2d-x的引用計數沒有影響的, 所以在上述**執行後, 雖然vector中儲存者sprite的指標, 但是其實都已經是野指標了, 所有的sprite實際已經析構調了. 這種情況相當危險. 把上述**中的vector改成cocos2d-x中的ccarray就可以解決上面的問題, 因為ccarray是對引用計數有影響的.

見下面的**:

ccarray

*sprites

=ccarray

::create

();for

(inti=

0;i<3;

++i)}

改動非常小, 僅僅是容器型別從c++原生容器換成了cocos2d-x從objc模擬過來的array, 但是這段**執行後, sprites中的sprite都可以正常的使用, 並且沒有問題. 可參考cocos2d-x的源**ccarray.cpp:

void

(ccarray

*arr

,ccobject

*object

)但是, 假如我就是想用c++原生容器, 不想用ccarray怎麼辦呢? 需要承擔的風險就來了, 有的時候還行, 比如上例, 我只需要去掉helloworld->release那一行, 並且明白此時所有權已經是屬於vector了, 在vector處理完畢後, 再release即可.

而有的時候這就沒有那麼簡單了. 特別是cocos2d-x因為依賴引用計數, 不僅僅是addchild等容器新增會增加引用計數, **的設計(模擬objc中的delegate)也會對引用計數有影響的. 曾經有人在初學cocos2d-x的時候, 問我cocos2d-x有沒有什麼設計問題, 有沒有啥坑, 我覺得這就是最大的乙個.

舉個簡單的例子, 我真心不喜歡引用計數, 所以全用c++的容器, 寫了下面這樣的**: (未編譯測試, 純示例使用)

class

enemy

~enemy

(){}

};class

enemymanager

~enemymanager

(){}

void

removeenemies()}

private:

vector

<

enemy

*>

enemies_

;};

剛開始的時候, 這只是一段和cocos2d-x完全沒有關係的**, 並且執行良好, 有一天, 我感覺的enmey其實是個sprite就方便操作了. 將enemy改為繼承自sprite, 那麼這段**就沒有那麼安全了, 因為enemymanager在完全不知道enemy的引用計數的情況下, 使用delete刪除了enmey, 假如此時還有其他地方對該enemy有引用, 就會crash. 雖然表面上看來是想新增一些ccsprite的顯示功能, 但是實際上, 一入此門(從ccobject繼承過來), 引用計數就已經無處不在, 此時需要把直接的delete改為呼叫release函式.

cocos2d-x起始也模擬了objc中的記憶體池, 但是因為不可能改變語言本身的特性, 那種簡單的語法糖語法就沒有, 需要的時候, 老實的操作ccpoolmanager和ccautoreleasepool吧. 在通常情況下, cocos2d-x增加的機制使得我們不太需要像在objc中那樣使用記憶體池. 我來解釋一下:

在cocos2d-x中, 幾乎所有有意義的類都有create函式, 比如sprite的create函式:

ccsprite

*ccsprite

::create

()cc_safe_delete

(psprite

);return

null

;}

基本只幹兩個事情, 乙個是new和init, 乙個就是呼叫autorelease函式講sprite本身加入記憶體池了. 此時講sprite加入記憶體池後, sprite的所有權已經屬於記憶體池了, 我們返回的指標其實是沒有所有權的. 在create出乙個類似物件後, 我們接下來的操作往往是吧這個物件再新增到parent node中(比如上層的scene或layer), 此時由記憶體池和這個parent node共同擁有這個sprite, 當sprite不需要再顯示的時候, 直接通過removechild將sprite從父節點中移除後, 就回到僅屬於記憶體池的情況了.

在objc中, 要是都是上面的情況, 我們又不手動的清理記憶體池, 這其實就已經有記憶體洩漏了, 但是cocos2d-x實際是每幀都幫我們清理記憶體池的. 也就是說, 每一幀僅僅屬於記憶體池的物件都會被釋放. 見下面的**:

void

ccdisplaylinkdirector

::mainloop

(void

)elseif(

!m_binvalid

)}

上面的**是ccdirector的遊戲主迴圈**, 主迴圈幹了件非常重要的事情, 那就是pop最上層的autorelease pool, 此時是在release全部僅僅由此記憶體池所有的物件. 就是依靠這樣的原理, 我們可以放心的將物件放在autorelease pool中, 知道在需要的時候, 這個物件就能正確的釋放, 同時只要有上層的父節點通過addchild對遊戲物件有了所有權以後, 又能正確的保證該物件不會被刪除.

本文原來是來自於給公司做的內部培訓材料, 因為一開始寫的很初略和簡單, 一直就沒想發布, 最近我在整理老的資料, 所以今天整理了一下, 新增了一些例子, 發布出來了, 可以明顯的看到後面的內容雖然更加重要, 但是寫的比前面要倉促, 有錯誤的話, 請各位不吝賜教.

cocos2dx 記憶體管理

記憶體管理中經常遇到的問題 記憶體洩露,記憶體溢位。在cocos2dx中用的是引用計數和自動釋放池的技術,由於熟悉objective c語言,所以對這兩個概念不會很陌生。一 引用計數 引用計數是自動記憶體管理的基礎 在物件裡增加乙個引用計數,當外部引用增加時,計數器加1,當外部引用消失時,計數器減1...

cocos2d x 記憶體管理

呼叫了autorelease的物件,將會在自動 池釋放的時候被釋放一次。因為這個操作發生在 mainloop drawscene 後,這時候遊戲中所有的邏輯已經執行完畢,正是釋放無效資源的最佳時機。所以乙個物件被create後,將被放進pool中,其ref 數為1,當遊戲整個邏輯跑完,如果沒有增加r...

cocos2dx 記憶體管理

我們知道,cocos2dx中使用了引用計數的方式去管理記憶體,不需要我們手動delete的去釋放記憶體。那麼cocos2dx中是怎麼實現引用計數的記憶體管理方式的呢?cocos2dx中的記憶體管理用到了兩個工具 引用計數器 ref 自動 池 autoreleasepool 引用計數器 ref ref...