要是徹底沒有接觸過Objc, 只是瞭解C++, 看到cocos2d-x的內存管理設計, 會想說髒話的. 瞭解objc的話, 起碼還能理解cocos2d-x的開發者是嘗試在C++中模擬Objc的內存管理方式. 不只僅是說加引用計數而已, 由於真要在C++中加引用計數的方法有不少種, cocos2d-x用的這種方法, 實在太不原生態了.node
由於cocos2d-x中牽涉到顯示的狀況最多, 我也就不拿CCArray這種東西作例子了, 看個CCSprite的例子吧, 用cocos2d-x的XCode template生成的HelloWorld工程中, 刪除原來的顯示代碼, 建立一個Sprite並顯示的代碼以下:算法
// part code of applicationDidFinishLaunching in AppDelegate.cpp // create a scene. it's an autorelease object CCScene *scene = HelloWorld::scene(); CCSprite *helloworld = new CCSprite; if (helloworld->initWithFile("HelloWorld.png")) { CCSize size = CCDirector::sharedDirector()->getWinSize(); // position the sprite on the center of the screen helloworld->setPosition( ccp(size.width/2, size.height/2) ); // add the sprite as a child to this layer scene->addChild(helloworld, 0); helloworld->release(); }
這裏暫時無論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")) { CCSize size = CCDirector::sharedDirector()->getWinSize(); // position the sprite on the center of the screen helloworld->setPosition( ccp(size.width/2, size.height/2) ); // add the sprite as a child to this layer scene->addChild(helloworld, 0); helloworld->release(); scene->removeChild(helloworld); }
上面的代碼helloworld sprite能正常的析構和釋放內存, 假如少了那句release的代碼就不行.app
這個部分是引用計數方法都會碰到的問題, 也就是引用計數到底在何時增長, 何時減小.
在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 (int i = 0; i < 3; ++i) { CCSprite *helloworld = new CCSprite; if (helloworld->initWithFile("HelloWorld.png")) { CCSize size = CCDirector::sharedDirector()->getWinSize(); // position the sprite on the center of the screen helloworld->setPosition( ccp(size.width/2, size.height/2) ); // add the sprite as a child to this layer scene->addChild(helloworld, 0); sprites.push_back(helloworld); helloworld->release(); scene->removeChild(helloworld); } }
由於C++的容器是對Cocos2d-x的引用計數沒有影響的, 因此在上述代碼運行後, 雖然vector中保存者sprite的指針, 可是其實都已是野指針了, 全部的sprite實際已經析構調了. 這種狀況至關危險. 把上述代碼中的vector改爲cocos2d-x中的CCArray就能夠解決上面的問題, 由於CCArray是對引用計數有影響的.
見下面的代碼:oop
CCArray *sprites = CCArray::create(); for (int i = 0; i < 3; ++i) { CCSprite *helloworld = new CCSprite; if (helloworld->initWithFile("HelloWorld.png")) { CCSize size = CCDirector::sharedDirector()->getWinSize(); // position the sprite on the center of the screen helloworld->setPosition( ccp(size.width/2, size.height/2) ); // add the sprite as a child to this layer scene->addChild(helloworld, 0); sprites->addObject(helloworld); helloworld->release(); scene->removeChild(helloworld); } }
改動很是小, 僅僅是容器類型從C++原生容器換成了Cocos2d-x從Objc模擬過來的array, 可是這段代碼執行後, sprites中的sprite均可以正常的使用, 而且沒有問題. 可參考cocos2d-x的源代碼ccArray.cpp:測試
/** Appends an object. Behavior undefined if array doesn't have enough capacity. */ void ccArrayAppendObject(ccArray *arr, CCObject* object) { CCAssert(object != NULL, "Invalid parameter!"); object->retain(); arr->arr[arr->num] = object; arr->num++; }
可是, 假如我就是想用C++原生容器, 不想用CCArray怎麼辦呢? 須要承擔的風險就來了, 有的時候還行, 好比上例, 我只須要去掉helloworld->release
那一行, 而且明白此時全部權已是屬於vector了, 在vector處理完畢後, 再release便可.
而有的時候這就沒有那麼簡單了. 特別是Cocos2d-x由於依賴引用計數, 不只僅是addChild等容器添加會增長引用計數, 回調的設計(模擬objc中的delegate)也會對引用計數有影響的. 曾經有人在初學Cocos2d-x的時候, 問我cocos2d-x有沒有什麼設計問題, 有沒有啥坑, 我以爲這就是最大的一個.
舉個簡單的例子, 我真心不喜歡引用計數, 因此全用C++的容器, 寫了下面這樣的代碼: (未編譯測試, 純示例使用)this
class Enemy { public: Enemy() {} ~Enemy() {} }; class EnemyManager { public: EnemyManager() {} ~EnemyManager() {} void RemoveEnemies() { for (auto it : enemies_) { delete *it; } } private: vector<Enemy*> enemies_; };
剛開始的時候, 這只是一段和Cocos2d-x徹底沒有關係的代碼, 而且運行良好, 有一天, 我感受的Enmey實際上是個Sprite就方便操做了. 將Enemy改成繼承自Sprite, 那麼這段代碼就沒有那麼安全了, 由於EnemyManager在徹底不知道enemy的引用計數的狀況下, 使用delete刪除了enmey, 假如此時還有其餘地方對該enemy有引用, 就會crash. 雖然表面上看來是想添加一些CCSprite的顯示功能, 可是實際上, 一入此門(從CCObject繼承過來), 引用計數就已經無處不在, 此時須要把直接的delete改成調用release函數.spa
cocos2d-x起始也模擬了objc中的內存池, 可是由於不可能改變語言自己的特性, 那種簡單的語法糖語法就沒有, 須要的時候, 老實的操做CCPoolManager和CCAutoreleasePool吧. 在一般狀況下, cocos2d-x增長的機制使得咱們不太須要像在objc中那樣使用內存池. 我來解釋一下:
在cocos2d-x中, 幾乎全部有意義的類都有create函數, 好比Sprite的create函數:設計
CCSprite* CCSprite::create() { CCSprite *pSprite = new CCSprite(); if (pSprite && pSprite->init()) { pSprite->autorelease(); return pSprite; } 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) { if (m_bPurgeDirecotorInNextLoop) { m_bPurgeDirecotorInNextLoop = false; purgeDirector(); } else if (! m_bInvalid) { drawScene(); // release the objects CCPoolManager::sharedPoolManager()->pop(); } }
上面的代碼是CCDirector的遊戲主循環代碼, 主循環幹了件很是重要的事情, 那就是pop最上層的autorelease pool, 此時是在release所有僅僅由此內存池全部的對象. 就是依靠這樣的原理, 咱們能夠放心的將對象放在autorelease pool中, 知道在須要的時候, 這個對象就能正確的釋放, 同時只要有上層的父節點經過addChild對遊戲對象有了全部權之後, 又能正確的保證該對象不會被刪除.
本文原來是來自於給公司作的內部培訓材料, 由於一開始寫的很初略和簡單, 一直就沒想發佈, 最近我在整理老的資料, 因此今天整理了一下, 添加了一些例子, 發佈出來了, 能夠明顯的看到後面的內容雖然更加劇要, 可是寫的比前面要倉促, 有錯誤的話, 請各位不吝賜教.