之前看过很多的文章,都是讲解cocos2dx的引用计数原理。有好有坏,今天我也自己来写一篇引用计数的文章,并且开始尝试阅读cocos2dx的源码,把自己的收获更新到这个博客,不为多少人看。只为自己记录这一路的点点滴滴。
讲了这么多那么开始吧!
首先就是贴上cocos2dx的万物之源 Ref类,这个类是Node的基类,而所有的显示节点类都是继承自Node类,所以他算是cocos2dx的根。
CCRef.h
class CC_DLL Ref
{
public:
void retain();
void release();
Ref* autorelease();
unsigned int getReferenceCount() const;
protected:
Ref();
public:
virtual ~Ref();
protected:
//用于计数的变量
unsigned int _referenceCount;
friend class AutoreleasePool;
#if CC_ENABLE_SCRIPT_BINDING
public:
unsigned int _ID;
int _luaID;
void* _scriptObject;
#endif
#if CC_USE_MEM_LEAK_DETECTION
public:
static void printLeaks();
#endif
};
CCRef.cpp
#include "base/CCRef.h"
#include "base/CCAutoreleasePool.h"
#include "base/ccMacros.h"
#include "base/CCScriptSupport.h"
#if CC_USE_MEM_LEAK_DETECTION
#include <algorithm> // std::find
#endif
NS_CC_BEGIN
#if CC_USE_MEM_LEAK_DETECTION
static void trackRef(Ref* ref);
static void untrackRef(Ref* ref);
#endif
Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
{
#if CC_ENABLE_SCRIPT_BINDING
static unsigned int uObjectCount = 0;
_luaID = 0;
_ID = ++uObjectCount;
_scriptObject = nullptr;
#endif
#if CC_USE_MEM_LEAK_DETECTION
trackRef(this);
#endif
}
Ref::~Ref()
{
#if CC_ENABLE_SCRIPT_BINDING
// if the object is referenced by Lua engine, remove it
if (_luaID)
{
ScriptEngineManager::getInstance()->getScriptEngine()->removeScriptObjectByObject(this);
}
else
{
ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
if (pEngine != nullptr && pEngine->getScriptType() == kScriptTypeJavascript)
{
pEngine->removeScriptObjectByObject(this);
}
}
#endif
#if CC_USE_MEM_LEAK_DETECTION
if (_referenceCount != 0)
untrackRef(this);
#endif
}
void Ref::retain()
{
CCASSERT(_referenceCount > 0, "reference count should greater than 0");
++_referenceCount;
}
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should greater than 0");
--_referenceCount;
if (_referenceCount == 0)
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
auto poolManager = PoolManager::getInstance();
if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
{
CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");
}
#endif
#if CC_USE_MEM_LEAK_DETECTION
untrackRef(this);
#endif
delete this;
}
}
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
unsigned int Ref::getReferenceCount() const
{
return _referenceCount;
}
#if CC_USE_MEM_LEAK_DETECTION
static std::list<Ref*> __refAllocationList;
void Ref::printLeaks()
{
if (__refAllocationList.empty())
{
log("[memory] All Ref objects successfully cleaned up (no leaks detected).\n");
}
else
{
log("[memory] WARNING: %d Ref objects still active in memory.\n", (int)__refAllocationList.size());
for (const auto& ref : __refAllocationList)
{
CC_ASSERT(ref);
const char* type = typeid(*ref).name();
log("[memory] LEAK: Ref object '%s' still active with reference count %d.\n", (type ? type : ""), ref->getReferenceCount());
}
}
}
static void trackRef(Ref* ref)
{
CCASSERT(ref, "Invalid parameter, ref should not be null!");
__refAllocationList.push_back(ref);
}
static void untrackRef(Ref* ref)
{
auto iter = std::find(__refAllocationList.begin(), __refAllocationList.end(), ref);
if (iter == __refAllocationList.end())
{
log("[memory] CORRUPTION: Attempting to free (%s) with invalid ref tracking record.\n", typeid(*ref).name());
return;
}
__refAllocationList.erase(iter);
}
#endif // #if CC_USE_MEM_LEAK_DETECTION
NS_CC_END
我们从一看出Ref这个类中有一个
protected:
//用于计数的变量
unsigned int _referenceCount;
这个变量就是记录引用次数,并且在构造函数中初始化时1。
在代码retain中可以看到只有简单增加这个计数器。
重要的来了,那就是release这个函数,我们看到他是在计数器为0的时候,delete自己。这样的设计源于自己创建自己销毁这个原则。
我们还可以看到 autoRelease 这个接口,他调用了PoolManager。
从字面上猜到,这个接口的作用应该是自动释放。
那么他是怎么实现的呢?
我们又找到了PoolManager的源码。
CCAutoreleasePool.h
#ifndef __AUTORELEASEPOOL_H__
#define __AUTORELEASEPOOL_H__
#include <stack>
#include <vector>
#include <string>
#include "base/CCRef.h"
NS_CC_BEGIN
class CC_DLL AutoreleasePool
{
public:
AutoreleasePool();
AutoreleasePool(const std::string &name);
~AutoreleasePool();
void addObject(Ref *object);
void clear();
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
bool isClearing() const { return _isClearing; };
#endif
bool contains(Ref* object) const;
void dump();
private:
std::vector<Ref*> _managedObjectArray;
std::string _name;
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
bool _isClearing;
#endif
};
class CC_DLL PoolManager
{
public:
CC_DEPRECATED_ATTRIBUTE static PoolManager* sharedPoolManager() { return getInstance(); }
static PoolManager* getInstance();
CC_DEPRECATED_ATTRIBUTE static void purgePoolManager() { destroyInstance(); }
static void destroyInstance();
AutoreleasePool *getCurrentPool() const;
bool isObjectInPools(Ref* obj) const;
friend class AutoreleasePool;
private:
PoolManager();
~PoolManager();
void push(AutoreleasePool *pool);
void pop();
static PoolManager* s_singleInstance;
std::vector<AutoreleasePool*> _releasePoolStack;
};
NS_CC_END
#endif //__AUTORELEASEPOOL_H__
CCAutoreleasePool.cpp
#include "base/CCAutoreleasePool.h"
#include "base/ccMacros.h"
NS_CC_BEGIN
AutoreleasePool::AutoreleasePool()
: _name("")
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
, _isClearing(false)
#endif
{
_managedObjectArray.reserve(150);
PoolManager::getInstance()->push(this);
}
AutoreleasePool::AutoreleasePool(const std::string &name)
: _name(name)
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
, _isClearing(false)
#endif
{
_managedObjectArray.reserve(150);
PoolManager::getInstance()->push(this);
}
AutoreleasePool::~AutoreleasePool()
{
CCLOGINFO("deallocing AutoreleasePool: %p", this);
clear();
PoolManager::getInstance()->pop();
}
void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object);
}
void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = true;
#endif
for (const auto &obj : _managedObjectArray)
{
obj->release();
}
_managedObjectArray.clear();
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = false;
#endif
}
bool AutoreleasePool::contains(Ref* object) const
{
for (const auto& obj : _managedObjectArray)
{
if (obj == object)
return true;
}
return false;
}
void AutoreleasePool::dump()
{
CCLOG("autorelease pool: %s, number of managed object %d\n", _name.c_str(), static_cast<int>(_managedObjectArray.size()));
CCLOG("%20s%20s%20s", "Object pointer", "Object id", "reference count");
for (const auto &obj : _managedObjectArray)
{
CC_UNUSED_PARAM(obj);
CCLOG("%20p%20u\n", obj, obj->getReferenceCount());
}
}
//--------------------------------------------------------------------
//
// PoolManager
//
//--------------------------------------------------------------------
PoolManager* PoolManager::s_singleInstance = nullptr;
PoolManager* PoolManager::getInstance()
{
if (s_singleInstance == nullptr)
{
s_singleInstance = new PoolManager();
// Add the first auto release pool
new AutoreleasePool("cocos2d autorelease pool");
}
return s_singleInstance;
}
void PoolManager::destroyInstance()
{
delete s_singleInstance;
s_singleInstance = nullptr;
}
PoolManager::PoolManager()
{
_releasePoolStack.reserve(10);
}
PoolManager::~PoolManager()
{
CCLOGINFO("deallocing PoolManager: %p", this);
while (!_releasePoolStack.empty())
{
AutoreleasePool* pool = _releasePoolStack.back();
delete pool;
}
}
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();
}
NS_CC_END
可以看到
AutoreleasePool* PoolManager::getCurrentPool()返回了一个AutoreleasePool的指针,<pre name="code" class="cpp">AutoreleasePool这个指针将在Ref autoRelease的时候添加到了一个叫<pre name="code" class="cpp">_managedObjectArray的vector里面去了。
那么自动管理是怎么做到呢?
转到CCDirector.cpp
<pre name="code" class="cpp">void DisplayLinkDirector::mainLoop()
{
if (_purgeDirectorInNextLoop)
{
_purgeDirectorInNextLoop = false;
purgeDirector();
}
else if (! _invalid)
{
drawScene();
// release the objects
PoolManager::getInstance()->getCurrentPool()->clear();
}
}
在这个文件的上面那个函数中,我们可以看到每个loop里面在渲染场景之后PoolManager做了一个操作,这个操作就是AutoreleasePool类里面的clear,
这个clear就是将维护的_managedObjectArray里面的每个对象执行release操作,最后把_managedObjectArray清空。
<span style="font-family: Arial, Helvetica, sans-serif;">看到这里我们大约知道了他的原理了。</span>
1、我们在创建Ref的时候计数器值是1;
2、执行retain的时候计数器加1,执行release的时候计数器减1,当计数器为0时由对象自己删除自己。
3、当我们执行autorelease的时候,Ref把释放的操作的权利给了AutoreleasePool这个友元类,就是如果我们没有任何retain操作,这个Ref的对象将在这一帧结束后被AutoreleasePool的clear给释放掉。
4、当我们在创建后又retain,那么将保留对这个Ref对象的控制权,只有在你release后才会被释放,因为AutoreleasePool只执行一次便不再参与该对象的内存管理。
<span style="font-family: Arial, Helvetica, sans-serif;">
</span>
<span style="font-family: Arial, Helvetica, sans-serif;">差不多我了解的cocos2dx引用计数的原理就是这么多了,博主的水平也是非常有限,哪里写得不好或出错,欢迎指正交流。</span>