1.引用计数
引用计数是现代内存管理中经常使用到的一个概念,它的基本思想是通过计数方式实现多个不同对象同时引用一个共享对象,具体的讲,当创建一个对象实例并在堆上分配内存时,对象的引用计数为1,在其他对象中需要持有这个共享对象时,需要把共享对象的引用计数+1,当其他对象不在持有该共享对象时,共享对象引用计数-1,当共享对象的引用计数变为0时,对象的内存会被立即释放。
在Cocos2d-x中,则定义了retain、release和autorelease函数,分别用于增加计数、减少计数以及将一个对象交给自动释放池对象AutoreleasePool进行管理,由AutoreleasePool对象负责调用release函数。
2.Ref类的讲解
在cocos中,节点类Node是绝大部分类的子类,如Sprite、Layer、Scene等,其父类为Ref类,Director类直接继承与Ref类,我们看Cocos源码就可以知道:
class CC_DLL Director : public Ref
class CC_DLL Node : public Ref
class CC_DLL Sprite : public Node, public TextureProtocol
class CC_DLL Layer : public Node
class CC_DLL Scene : public Node
retain()、release()和autorelease()引用计数相关函数都是Ref类的成员函数,下面就通过cocos源码对Ref类进行讲解
先看CCRef头文件:
这个是内存泄露检测的开关,关于内存泄露检测方面的代码会通过这个宏开关控制:
#define CC_REF_LEAK_DETECTION 0
Clonable类定义了复制Ref类对象的规约,完成子类的复制工作。3.0以上版本建议使用clone函数,copy函数已属于废弃函数。
class CC_DLL Clonable
{
public:
//返回一个copy的Ref对象,纯虚函数,需要子类重写该函数
virtual Clonable* clone() const = 0;
virtual ~Clonable() {};
//被废弃
CC_DEPRECATED_ATTRIBUTE Ref* copy() const
{
CC_ASSERT(false);
return nullptr;
}
};
可以看到Ref类成员函数主要有4个:
1.void retain();//引用计数+1
2.void release();//引用计数-1
3.Ref* autorelease();//设置实例对象的释放由内存管理器进行管理,实现自动释放
4.unsigned int getReferenceCount() const;//获得引用计数
另外我们看到这句:
friend class AutoreleasePool;
设定AutoreleasePool类为友元类,这是一个通过CCObject指针容器CCMutableArray来对CCObject实例对象的内存进行管理的类,CCMutableArray在加入CCObject对其引用计数+1,在移除CCObject对其引用计数-1
此为还可以看到打印内存泄露信息的方法:
#if CC_REF_LEAK_DETECTION
public:
static void printLeaks();
#endif
};
好了,我们再去CCRef.cpp中好好看看:
构造函数:
Ref::Ref(): _referenceCount(1) // 当创建出Ref,其引用计数默认为1
{
#if CC_ENABLE_SCRIPT_BINDING
//定义一个静态UINT类型变量作为实例对象计数器,此值只会增长,不会减少,保证唯一
static unsigned int uObjectCount = 0;
//脚本对象的ID
_luaID = 0;
//注意,所有由此CCObject类派生的子类也会拥有这个唯一的ID,它可以使我们通过唯一的ID来获取相应的实例对象
_ID = ++uObjectCount;
_scriptObject = nullptr;
#endif
#if CC_REF_LEAK_DETECTION
//加入到保存Ref的list中
//打印内存泄露相关信息会用到该list
trackRef(this);
#endif
}
析构函数,就是拿脚本的一些对象进行析构:
Ref::~Ref()
{
#if CC_ENABLE_SCRIPT_BINDING
// 如果这个对象被lua脚本引擎引用了,就删除掉
if (_luaID)
{
ScriptEngineManager::getInstance()->getScriptEngine()->removeScriptObjectByObject(this);
}
else
{
//获得引擎脚本
ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
//如果这个引擎脚本是javascript,删除对象
if (pEngine != nullptr && pEngine->getScriptType() == kScriptTypeJavascript)
{
pEngine->removeScriptObjectByObject(this);
}
}
#endif
//从保存Ref的list中删除,内存泄露检测相关
#if CC_REF_LEAK_DETECTION
if (_referenceCount != 0)
untrackRef(this);
#endif
}
retain()对其引用计数值进行+1:
void Ref::retain()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
++_referenceCount;
}
release()先对引用计数-1,然后判断引用计数是否为0,若为0则删除对象:
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
//对其引用计数值进行-1
--_referenceCount;
//引用计数为0,删除对象
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
//从保存Ref*的list中删除
#if CC_REF_LEAK_DETECTION
untrackRef(this);
#endif
//删除该对象
delete this;
}
}
autorelease()将节点添加到自动释放池中:
Ref* Ref::autorelease()
{
//调用内存管理PoolManager的单例对象中存储的自动释放池对象的addObject函数加入当前的Ref实例对象的指针
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
获得引用计数:
unsigned int Ref::getReferenceCount() const
{
return _referenceCount;
}
此外,我们还看到一些代码,这些代码是检测内存泄露的,只有在调试的时候使用到,一般实际开发中用不到太多:
首先声明一个list:
static std::list __refAllocationList;
打印保存在list中的对象:
void Ref::printLeaks()
{
// 如果是空的不做处理
if (__refAllocationList.empty())
{
log("[memory] All Ref objects successfully cleaned up (no leaks detected).\n");
}
else //不为空
{
//打印__refAllocationList的长度
log("[memory] WARNING: %d Ref objects still active in memory.\n", (int)__refAllocationList.size());
//循环打印Ref对象的信息
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());
}
}
}
把Ref对象添加到list中:
static void trackRef(Ref* ref)
{
CCASSERT(ref, "Invalid parameter, ref should not be null!");
__refAllocationList.push_back(ref);
}
把添加的Ref对象释放掉:
static void untrackRef(Ref* 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;
}
//从list中释放Ref
__refAllocationList.erase(iter);
}
最后,总结一下:
1.Cocos2d-x中使用引用计数进行内存管理;
2.Ref类是Node的父类,Node类是Sprite、Layer、Scene父类,Director类直接继承自Ref类;
3.对象的引用计数是Ref类中的成员变量,是个int值;
4.Ref类中提供了 retain()、release()、autorelease()、getReferenceCount()方法对引用计数进行操作;
5.Ref::autorelease()方法是将本ref对象添加到自动释放池对象中;
6.Ref类提供了检测内存泄露的相关方法;
7.内存管理类PoolManager的实例对象以及自动释放池AutoreleasePool类对象进行管理。
以上,下一篇博客我们就看看内存管理类PoolManager以及自动释放池AutoreleasePool是怎样进行管理的。