本文首先介紹 Cocos2d-x 3.2 中記憶體管理的作用,以及各個作用的應用。借由通俗易懂的解釋來了解記憶體管理的過程。其次通過源碼解析介紹其内部的實作原理。加深了解,進而在有需要的時候繞開引擎建立自己的記憶體管理機制。
一、Cocos2d-x 3.2 記憶體管理的兩個方面
1) 通過加入
autorelease
來自動釋放那些建立後未使用的對象。
2) 通過節點管理來保證對象在棄用後及時地删除。
1、及時釋放棄用的對象
使用條件:該對象是Node的子類對象
使用方法:addChild、removeChild
記憶體管理過程:
addChild //添加對象後,對象可以被使用
removeChild //删除對象後,對象被立刻删除(通過 delete)
2、及時釋放未使用的對象
簡述:新建立的對象如果一幀内不使用,就會被自動釋放。(所謂一幀,即是一個gameloop。)
使用條件:對象通過
CREAT_FUNC()
宏建立或者對象使用
autorelease
加入了自動釋放池。
使用方法:自動實作
記憶體管理過程:
- 對象不使用的情況
對象建立 引用+1 對象自動釋放 引用-1
- 對象使用的情況
對象建立 引用+ 對象使用 引用+ // 通過 addChild 使用對象 對象自動釋放 引用-
引用的初始值為0,如果一幀結束後對象的引用值還是0,那就就會被 delete。
^^ 版權銜接線 vv 全文出處:http://www.cnblogs.com/tangyikejun/p/4361638.html
二、記憶體管理的實作原理
涉及記憶體管理的檔案很多,僅展示直接相關的部分代碼。
1、第一部分
Ref
類: 進行引用計數、提供加入自動釋放池的接口。
AutoreleasePool
類: 管理一個
vector
數組來存放加入自動釋放池的對象。提供對釋放池的清空操作。
PoolManager
類: 管理一個
vector
數組來存放自動釋放池。預設情況下引擎隻建立一個自動釋放池,是以這個類是提供給開發者使用的,例如出于性能考慮添加自己的自動釋放池。
DisplayLinkDirector
類: 這是一個導演類,提供遊戲的主循環,實作每一幀的資源釋放。這個類的名字看起來有點怪,但是不用管它。因為這個類繼承了
Director
類,也是唯一一個繼承了
Director
的類,也就是說完全可以合并為一個類,引擎開發者在源碼中有部分說明。
1.1 Ref
// 引用計數變量
unsigned int _referenceCount;
// 對象被構造後,引用計數值為 1
Ref::Ref()
: _referenceCount() //當Ref對象被建立時,引用計數的值為 1
{
#if CC_ENABLE_SCRIPT_BINDING
static unsigned int uObjectCount = ;
_luaID = ;
_ID = ++uObjectCount;
_scriptObject = nullptr;
#endif
#if CC_USE_MEM_LEAK_DETECTION
trackRef(this);
#endif
}
// 引用+1
void Ref::retain()
{
CCASSERT(_referenceCount > , "reference count should greater than 0");
++_referenceCount;
}
// 引用-1 。如果引用為0則釋放對象
void Ref::release()
{
CCASSERT(_referenceCount > , "reference count should greater than 0");
--_referenceCount;
if (_referenceCount == )
{
#if CC_USE_MEM_LEAK_DETECTION
untrackRef(this);
#endif
delete this; // 注意這裡 把對象 delete 了
}
}
// 提供加入自動釋放池的接口。對象調用此函數即可加入自動釋放池的管理。
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
//擷取引用計數值
unsigned int Ref::getReferenceCount() const
{
return _referenceCount;
}
1.2 AutoreleasePool
// 存放釋放池對象的數組
std::vector<Ref*> _managedObjectArray;
// 往釋放池添加對象
void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object);
}
// 清空釋放池,将其中的所有對象都 delete
void AutoreleasePool::clear()
{
// 釋放所有對象
for (const auto &obj : _managedObjectArray)
{
obj->release();
}
// 清空vector數組
_managedObjectArray.clear();
}
// 檢視某個對象是否在釋放池中
bool AutoreleasePool::contains(Ref* object) const
{
for (const auto& obj : _managedObjectArray)
{
if (obj == object)
return true;
}
return false;
}
1.3 PoolManager
// 釋放池管理器單例對象
static PoolManager* s_singleInstance;
// 釋放池數組
std::vector<AutoreleasePool*> _releasePoolStack;
// 擷取 釋放池管理器的單例
PoolManager* PoolManager::getInstance()
{
if (s_singleInstance == nullptr)
{
// 建立一個管理器對象
s_singleInstance = new PoolManager();
// 添加一個自動釋放池
new AutoreleasePool("cocos2d autorelease pool");// 内部使用了釋放池管理器的push,這裡的調用很微妙,讀者可以動手看一看
}
return s_singleInstance;
}
// 擷取目前的釋放池
AutoreleasePool* PoolManager::getCurrentPool() const
{
return _releasePoolStack.back();
}
// 檢視對象是否在某個釋放池内
bool PoolManager::isObjectInPools(Ref* obj) const
{
for (const auto& pool : _releasePoolStack)
{
if (pool->contains(obj))
return true;
}
return false;
}
// 添加釋放池對象
void PoolManager::push(AutoreleasePool *pool)
{
_releasePoolStack.push_back(pool);
}
// 釋放池對象出棧
void PoolManager::pop()
{
CC_ASSERT(!_releasePoolStack.empty());
_releasePoolStack.pop_back();
}
1.4 DisplayLinkDirector
void DisplayLinkDirector::mainLoop()
{
//第一次當導演
if (_purgeDirectorInNextLoop)
{
_purgeDirectorInNextLoop = false;
purgeDirector();//進行清理工作
}
else if (! _invalid)
{
// 繪制場景,遊戲主要工作都在這裡完成
drawScene();
// 清空資源池
PoolManager::getInstance()->getCurrentPool()->clear();
}
}
根據目前的分析,我們先來捋一捋,待會兒再進一步深入。記憶體管理的過程是怎麼樣的呢?首先,建立了一個
Node
對象A,
Node
繼承Ref,是以
Ref
的引用計數為1;然後,A通過
autorelease
将自己放入自動釋放池;
drawScene()
完成後,一幀結束,
Director
通過釋放池将池中的對象
clear()
,即對
Node
對象A進行
release()
操作。A的引用計數變為0,執行
delete
釋放A對象。
接下來我們繼續介紹另外幾個與記憶體管理有關的類。
2、第二部分
Node
類:提供了
addChild
和
removeChild
方法來建立遊戲的節點樹。
Vector
類:封裝了對于對象的
retain
操作和
release
操作。
2.1 Node
// 添加節點
void Node::addChild(Node *child)
{
CCASSERT( child != nullptr, "Argument must be non-nil");
this->addChild(child, child->_localZOrder, child->_name); // 經過這個方法-->addChildHelper-->insertChild,完成retain操作
}
// 移除節點
void Node::removeChild(Node* child, bool cleanup /* = true */)
{
//
if (_children.empty())
{
return;
}
//
ssize_t index = _children.getIndex(child);
if( index != CC_INVALID_INDEX )
this->detachChild( child, index, cleanup );//注意這個函數
}
// 插入節點
void Node::insertChild(Node* child, int z)
{
_transformUpdated = true;
_reorderChildDirty = true;
_children.pushBack(child);// pushBack方法對節點進行了retain
child->_setLocalZOrder(z);
}
// 剝離節點
void Node::detachChild(Node *child, ssize_t childIndex, bool doCleanup)
{
...// 部分省略
_children.erase(childIndex);// erase方法對節點進行了release
}
2.2 Vector
// 這裡僅展示與Node類相關的記憶體管理的部分
// 将對象入棧,引用+
void pushBack(T object)
{
CCASSERT(object != nullptr, "The object should not be nullptr");
_data.push_back( object );
object->retain(); // 進行了retain
}
// 将目标位置的對象移除
iterator erase(ssize_t index)
{
CCASSERT(!_data.empty() && index >= && index < size(), "Invalid index!");
auto it = std::next( begin(), index );
(*it)->release(); // 進行了release
return _data.erase(it);
}
到這裡,終于可以把故事完整地講一遍了。記憶體管理的過程是怎麼樣的呢?首先,建立了一個
Node
對象A,
Node
繼承
Ref
,是以
Ref
的引用計數為1;然後A又通過
autorelease
将自己放入自動釋放池;接着,有個
Node
對象B,B通過
addChild(A)
使得A的引用+1;幾個
mainLoop
後,B通過
removeChild(A)
使得A的引用-1;這個
mainLoop
的
drawScene()
完成後,一幀結束,
Director
通過釋放池将池中的對象
clear()
,即對
Node
對象A進行
release
操作。A的引用計數變為0,執行
delete
釋放A對象。
3、高階用法
之是以稱為高階用法,是因為,如果開發者對 Cocos 的記憶體管理機制了解不夠深刻,那麼很可能會用錯而導緻損失大于收益。另一方面,這類用法在平時很少會用到。
3.1 使用 retain
來延長對象的生存時間
retain
在開發過程中,如果需要使用一個節點對象,但是又不想把它放到節點樹裡面去。那麼就可以使用
retain
來避免對象被自動釋放掉。
3.2 使用 PoolManager
的 push
來延長對象的生存時間
PoolManager
push
有些情況下,希望閑置對象晚一幀進行銷毀,可以使用
push
把目前釋放池推入棧底,那麼這一幀結束的時候隻會釋放剛
push
進去的釋放池。
筆者本身還沒有機會使用過高階用法,如果有小夥伴發現了高階用法在實際問題中的應用,敬請留言交流。
源位址:http://www.cnblogs.com/tangyikejun/p/4361638.html?utm_source=tuicool