cocos2dx面試時最容易考的問題就是內存管理,我自己都被問的煩了,本文分析透徹,源碼詳盡,可以避免在這個問題上失分
cocos2dx的內存管理採用引用計數的策略,百度百科的引用計數解釋如下:
通過源代碼分析,作出以下總結:
1.Ref類中的_referenceCount成員變量用作引用計數
protected:
/// count of references
unsigned int _referenceCount;
2.Ref的構造函數爲_referenceCount賦值爲1
Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
3.retain() 執行++_referenceCount,release()執行--_referenceCount,並且當_referenceCount ==0時,釋放對象.
void Ref::retain()
{
++_referenceCount;
}
void Ref::release()
{
--_referenceCount;
if (_referenceCount == 0)
{
delete this;
}
}
4.autorelease()會在自動釋放池中,添加一個對象(向量尾部插入新對象),AutoreleasePool類中,使用向量容器vector<Ref*> _managedObjectArray保存對象。
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object);
}
std::vector<Ref*> _managedObjectArray;
5.通過create()創建的對象,會執行new(),init(),autorelease()幾個函數,即對象被放入自動釋放池,這類對象若不addchild()會在下一幀釋放,若想保留可手動retain(),不需要時手動release()
Scene* Scene::create()
{
Scene *ret = new (std::nothrow) Scene();
if (ret && ret->init())
{
ret->autorelease();
return ret;
}
else
{
CC_SAFE_DELETE(ret);
return nullptr;
}
}
6.導演類的mainLoop()執行drawScene()畫完一幀後,執行 PoolManager::getInstance()->getCurrentPool()->clear();clear()有2個功能:
(1)
std::vector<Ref*> releasings;
releasings.swap(_managedObjectArray);
用新向量releasings和向量_managectArray互換,將_managedObjectArray內容清空,長度清0,這麼做是爲了讓自動釋放池中的對象只自動執行一次release()判斷是否釋放
(2)
for (const auto &obj : releasings)
{
obj->release();
}
遍歷自動釋放池中的對象逐一調用一次release(),完整代碼如下:
void DisplayLinkDirector::mainLoop()
{
if (_purgeDirectorInNextLoop)
{
_purgeDirectorInNextLoop = false;
purgeDirector();
}
else if (_restartDirectorInNextLoop)
{
_restartDirectorInNextLoop = false;
restartDirector();
}
else if (! _invalid)
{
drawScene();//畫一幀
// release the objects
PoolManager::getInstance()->getCurrentPool()->clear();
}
}
void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = true;
#endif
std::vector<Ref*> releasings;
releasings.swap(_managedObjectArray);
for (const auto &obj : releasings)
{
obj->release();
}
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = false;
#endif
}
7.addChild(Node *child)會執行child->retain();removechild()會執行child->release();
void Node::addChild(Node *child)
{
CCASSERT( child != nullptr, "Argument must be non-nil");
this->addChild(child, child->_localZOrder, child->_name);
}
void Node::addChild(Node* child, int localZOrder, const std::string &name)
{
CCASSERT(child != nullptr, "Argument must be non-nil");
CCASSERT(child->_parent == nullptr, "child already added. It can't be added again");
addChildHelper(child, localZOrder, INVALID_TAG, name, false);
}
void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag)
{
if (_children.empty())
{
this->childrenAlloc();
}
this->insertChild(child, localZOrder);
//以下省略
}
void Node::insertChild(Node* child, int z)
{
_transformUpdated = true;
_reorderChildDirty = true;
_children.pushBack(child);
child->_localZOrder = z;
}
void pushBack(T object)
{
CCASSERT(object != nullptr, "The object should not be nullptr");
_data.push_back( object );
object->retain();
}
8.當對象的指針作爲形參時,函數體內需retain()形參 (形參被其他指針保留了,所以需retain),
release被賦值指針(被賦值指針不需要使用原先指向的對象了,所以需release),再賦值.
void testFun(Ref* obj1)
{
obj1->retain();
obj2->release();
obj2 = obj1;
}
面試環節
問:cocos2d-x內存管理機制如何實現?
答:cocos2d-x使用引用計數機制實現內存自動管理。有一個基類Ref,Ref中通過referenceCount變量保存計數,構造函數中會把新建對象的計數賦值爲1,Ref中retain可將計數自加,release可將計數自減,並且判斷當計數爲0時進行釋放。新建出的節點加到父節點時會調用retain,從父節點移除時會調用release。有一個自動釋放池類AutoreleasePool,其中有個autorelease函數,可將對象放到自動釋放池的一個向量容器中,通過CREATE宏創建出來的函數都會執行autorelease操作。在每一幀結束時會調用自動釋放池的clear函數,clear會將自動釋放池中所有對象調用release函數。因此新建出來的對象其引用計數的變化過程爲:創建出來構造函數賦值爲1,加到父節點後變爲2,所在幀結束時由於調用了clear又變爲1這樣一個過程。