Ref类
如果把Cocos2d比喻为一颗大树,那么Ref类可以说是这颗大树的根,Cocos2d中大部分类的源头都是Ref类,也就是说Ref是一个祖先类。它的主要作用是使用引用计数来管理资源,有点像shared_ptr。我们把其中最重要的代码截出来,如下所示。
class Ref
{
public:
void retain();
void release();
Ref* autorelease();
unsigned int getReferenceCount() const;
protected:
Ref();
public:
virtual ~Ref();
protected:
unsigned int _referenceCount;
friend class AutoreleasePool;
};
Ref::Ref()
: _referenceCount(1)
{
}
Ref在构造函数的时候将引用计数设置为1。
Ref::~Ref()
{
#if CC_ENABLE_SCRIPT_BINDING
ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
if (pEngine != nullptr && _luaID)
{
// if the object is referenced by Lua engine, remove it
pEngine->removeScriptObjectByObject(this);
}
#if !CC_ENABLE_GC_FOR_NATIVE_OBJECTS
else
{
if (pEngine != nullptr && pEngine->getScriptType() == kScriptTypeJavascript)
{
pEngine->removeScriptObjectByObject(this);
}
}
#endif // !CC_ENABLE_GC_FOR_NATIVE_OBJECTS
#endif // CC_ENABLE_SCRIPT_BINDING
#if CC_REF_LEAK_DETECTION
if (_referenceCount != 0)
untrackRef(this);
#endif
}
Ref在析构时移除了脚本(如果你是用脚本语言写的话)。
void Ref::retain()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
++_referenceCount;
}
在调用retain()时会判断引用计数是否大于零,如果否则启动断言,如果是则程序继续执行,然后引用计数加一。这个函数用来增加引用计数且每次调用只增加1。
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should be 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_ENABLE_SCRIPT_BINDING
ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
if (pEngine != nullptr && pEngine->getScriptType() == kScriptTypeJavascript)
{
pEngine->removeObjectProxy(this);
}
#endif // CC_ENABLE_SCRIPT_BINDING
#if CC_REF_LEAK_DETECTION
untrackRef(this);
#endif
delete this;
}
}
release是retain的反向操作,它会将引用计数加一,但是它同时还兼顾着其它的责任,来逐句分析:
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
从代码可以看出,此函数将当前对象放入了当前自释放内存池管理器。
unsigned int Ref::getReferenceCount() const
{
return _referenceCount;
}
当前引用计数的get方法,用来获得当前引用计数。
总结
Ref的主要代码就已经解析完了,那么Ref的具体作用是什么呢?我们可以来看一个场景,一个游戏角色从A地图传送到B地图,在代码里面就是一个Sprite在两个Scene里面传递,这是我们使用的一定是引用传递,然后人物如果再返回A地图,那么当前B地图就要将资源释放,游戏人物也将一起释放,由于是引用传递,那么人物的内存也将释放,这是在A地图中人物就不存在了。所以为了防止这种情况发生,就要用到引用计数,每次涉及到引用传递时就将计数加一,释放引用内存时先将引用计数减一,只有当引用计数为0时再释放内存,否则只减少引用计数而不释放内存。
但是内存管理并不是只有引用计数就行了,还得搭配智能内存池才可以,下一节中将会进行解析。
扩展
我们再Ref类还看到一段有意思的代码,它被CC_REF_LEAK_DETECTION宏控制着,我们如果需要使用可以把CC_REF_LEAK_DETECTION宏设置为1,这段代码被用来进行泄露检测,函数部分其实在Ref的成员函数中也出现过,但是为了方便解析就没有提及,现在我们来把他单独拿出来,把宏控制的代码块单独来看。
static std::vector __refAllocationList;
static std::mutex __refMutex;
void Ref::printLeaks()
{
std::lock_guard refLockGuard(__refMutex);
// Dump Ref object memory leaks
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)
{
std::lock_guard refLockGuard(__refMutex);
CCASSERT(ref, "Invalid parameter, ref should not be null!");
// Create memory allocation record.
__refAllocationList.push_back(ref);
}
static void untrackRef(Ref* ref)
{
std::lock_guard refLockGuard(__refMutex);
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);
}
代码使用了mutex锁说明是线程安全的。
__refAllocationList是用来存储Ref的分配链表。
用来打印泄露信息,如果__refAllocationList为空那么就不存在泄露,如果不为空就打印泄露的对象。
为空:
if (__refAllocationList.empty())
{
log("[memory] All Ref objects successfully cleaned up (no leaks detected).\n");
}
不为空:
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)
{
std::lock_guard refLockGuard(__refMutex);
CCASSERT(ref, "Invalid parameter, ref should not be null!");
// Create memory allocation record.
__refAllocationList.push_back(ref);
}
向__refAllocationList中存入Ref
static void untrackRef(Ref* ref)
{
std::lock_guard refLockGuard(__refMutex);
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);
}
如果__refAllocationList中存在传入的Ref就删除。
我们可以看见在Ref的构造函数Ref::Ref()中调用了一次trackRef(this),说明把当前对象存入了__refAllocationList中,在析构函数Ref::~Ref()中调用了一次untrackRef(this),说明要想从__refAllocationList中删除就必须彻底释放此对象,否则就存在泄露的可疑,这样泄露检测就完成了,可以说是一个很巧妙的设计。