【cocos2d-x 3.x 學習筆記】對象記憶體管理,cocos2d-x3.x
記憶體管理
記憶體管理一直是一個不易處理的問題,開發人員必須考慮分配回收的方式和時機,針對堆和棧做不同的最佳化處理,等等。記憶體管理的核心是動態分配的對象必須保證在使用完畢後有效地釋放記憶體,即管理對象的生命週期。由於C++是一個較為底層的語言,其設計上不包含任何智能管理記憶體的機制。一個對象在使用完畢後必須被回收,然而在複雜的程式中,對象所有權在不同程式片段間傳遞或共用,使得確定回收的時機十分困難,因此記憶體管理成為了程式員十分頭疼的問題。
另一方面,過於零散的對象分配回收可能導致堆中的記憶體片段化,降低記憶體的使用效率。因此,我們需要一個合適的機制來緩解這個問題。
Boost庫引入的智能指標(以及C++11引入的共用指標)從對象所有權傳遞的角度來解決記憶體管理問題。但是,在很多情況下,智能指標還是顯得單薄而無力,因為實際開發中對象間的關係十分複雜,所有權傳遞的操作在開發過程中會變得冗雜不堪。於是,各種基於C++的第三方工具庫和引擎往往都會實現自己的智能記憶體管理機制來解決記憶體管理的難題,試圖將開發人員從煩瑣而晦澀的記憶體管理中解放出來。
主流的記憶體管理技術
目前,主要有兩種實現智能管理記憶體的技術,一是引用計數,二是記憶體回收。
1)引用計數它是一種很有效機制,通過給每個對象維護一個引用計數器,記錄該對象當前被引用的次數。當對象增加一次引用時,計數器加1;而對象失去一次引用時,計數器減1;當引用計數為0時,標誌著該對象的生命週期結束,自動觸發對象的回收釋放。引用計數的重要規則是每一個程式片段必須負責任地維護引用計數,在需要維持對象生存的程式段的開始和結束分別增加和減少一次引用計數,這樣我們就可以實現十分靈活的智能記憶體管理了。實際上,C++中的std::shared_ptr內部就是通過引用計數實現。
2)記憶體回收它通過引入一種自動的記憶體回收器,試圖將程式員從複雜的記憶體管理工作中完全解放出來。它會自動跟蹤每一個對象的所有引用,以便找到所有正在使用的對象,然後釋放其餘不再需要的對象。記憶體回收行程還可以壓縮使用中的記憶體,以縮小堆所需要的工作空間。記憶體回收可以防止記憶體泄露,有效地使用可用記憶體。但是,記憶體回收行程通常是作為一個單獨的低層級的線程啟動並執行,在不可預知的情況下對記憶體堆中已經死亡的或者長時間沒有使用過的對象進行清除和回收,程式員不能手動指派記憶體回收行程回收某個對象。回收機制包括分代複製記憶體回收、標記記憶體回收和增量記憶體回收等,一般記憶體回收機制應用在受託管的語言中(即運行在虛擬機器中),如C#、Java以及一些指令碼語言。
Cocos2d-x的記憶體管理
cocos2d-x中使用的是上面的引用計數來管理記憶體,但是又增加了一些自己的特色(自動回收池)。
cocos2d-x中通過Ref類(在2.x中是CCObject)來實現引用計數,所有需要實現記憶體自動回收的類都應該繼承自Ref類。對象建立時引用計數為1,對象拷貝時引用計數加1,對象釋放時引用計數減1,如果引用計數為0時釋放對象記憶體。下面是Ref類的定義(為了簡潔去掉了其中的Assert 陳述式,但不影響其代碼完整性):
class CC_DLL Ref{public: // 將引用計數加1 void retain() { ++_referenceCount; } // 將引用技術減1,如果引用計數為0,釋放當前對象 void release() { --_referenceCount; if (_referenceCount == 0) { delete this; } } // 將當前對象加入到當前的自動回收池中 Ref* autorelease() { PoolManager::getInstance()->getCurrentPool()->addObject(this); return this; } unsigned int getReferenceCount() const { return _referenceCount; }protected: Ref() : _referenceCount(1) // when the Ref is created, the reference count of it is 1 {}public: virtual ~Ref();protected: /// count of references unsigned int _referenceCount; friend class AutoreleasePool;};在cocos2d-x中建立對象通常有兩種方式:
1)auto sprite = new Sprite;2)auto sprite = Sprite::create();Object *Object::create(){ Object *obj = new Object; if (obj && obj->init()) { obj->autorelease(); } else { delete obj; obj = nullptr; } return obj;}這兩中方式的差異可以參見我另一篇博文“【cocos2d-x 3.x 學習筆記 02】對象建立方式討論”。在cocos2d-x中提倡使用第二種方式,為了避免誤用第一種方式,一般將建構函式設為 protected 或 private。
使用靜態工廠方式 create() 建立對象,對象建立成功後會調用對象的autorelease()方法將對象加入到當前自動回收池中。情境每一幀繪製結束時會遍曆當前自動回收池中的所有對象執行其release()方法,然後清空自動回收池中的對象。通過下面的函數調用順序可以找到每一幀繪製結束時,清理自動回收池的地方:
// main.cppreturn Application::getInstance()->run();// Appdelegate.cppint Application::run(){ ... while(!glview->windowShouldClose()) { QueryPerformanceCounter(&nNow); if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart) { nLast.QuadPart = nNow.QuadPart; // Enter loop director->mainLoop(); glview->pollEvents(); } else { Sleep(0); } } ...}// CCDirector.cppvoid DisplayLinkDirector::mainLoop(){ if (_purgeDirectorInNextLoop) { _purgeDirectorInNextLoop = false; purgeDirector(); } else if (! _invalid) { drawScene(); // release the objects PoolManager::getInstance()->getCurrentPool()->clear(); }}// CCAutoReleasePool.cppvoid AutoreleasePool::clear(){ for (const auto &obj : _managedObjectArray) { obj->release(); } _managedObjectArray.clear(); // std::vector<cocos2d::Ref *> 類型的數組}如果對象通過 create() 建立後沒有使用,其預設引用計數為1,在情境第一幀繪製結束時會調用對象的release()方法將引用計數減1至0,由於引用計數為0釋放對象所佔用記憶體,同時將對象從自動回收池中清除掉。
如果對象通過 create() 建立後,通過addChild()加入某個父節點或直接調用對象的retain()函數,將導致對象的引用計數加1。在情境第一幀繪製結束時會調用對象的release()方法將引用計數減1,同時將對象從自動回收池中清除掉。此時對象的引用計數為1,但是對象已經不在自動回收池中,所以下一幀情境繪製清空自動回收池時對其沒有影響,其佔用的記憶體空間不會被自動釋放,除非主動調用其release()函數將引用計數再次減1,從而使其引用計數變為0被釋放(removeChild()或父節點被釋放,這些操作內部實際會調用當前子節點的release()函數)。
所以,通過create()建立對象後,要麼將其加入到其他節點中,要麼調用對象的retian()函數,否則在其他地方(如事件回呼函數)調用時,由於對象在一幀結束時被釋放,會發生引用野指標拋出異常的情況。
最佳實務
如果想獲得cocos2d-x的自動記憶體管理,就最好不要直接使用對象的 retain() 和 release()函數,如果確實需要那麼也要成對的使用,調用一次retain()就必須在合適的時機和地方調用一次其release()函數。
參考資料:[1]cocos2d-x 進階開發教程 2.3 節
[2]cocos2d-x 3.x 原始碼
這本書是基於Cocos2dx的哪個版本,2x還是3x
這個是一個開源社區開發的,不是哪個公司開發的。
到它首頁上,看最下面一行就知道了。
“Cocos2D-x 開源開發人員社區”
cocos2d-x中情境切換後情境對象是否自動釋放佔用記憶體?
replaceScene情境以後你的情境會自動釋放,這個架構已經幫我們做好處理。