引擎采用引用计数的机制管理内存,引用计数的最核心思想就是标识一块内存有多少人在使用它(所有权),当没有人使用时它就被释放。Ref、PoolManager和AutoreleasePool它们联起手来负责这项工作,Ref最直接的负责增减对象的引用计数,它其中还提供了一个自动释放对象的方法,这就用到了PoolManger和AutoreleasePool。PoolManager可以管理多个AutoreleasePool,而始终只有一个当前的AutoreleasePool有效。AutoreleasePool则负责存放需要被自动释放的对象,在每帧结束的时候clear()它们(这个会在AutoreleasePool::clear()中详细说明)。
看看他们的继承关系:
<img title = 'QQ浏览器截屏未命名.png' src='http://cdn.cocimg.com/bbs/attachment/Fid_41/41_410339_1766737cff17450.png' >
可以看到,几乎所有的对象类都继承自Ref。而PoolManager和AutoreleasePool各自成一类,它们只是为了辅助Ref的自动释放对象功能而存在的。
Ref:
1、成员变量:
protected:
unsigned int _referenceCount; // 对象的引用计数,增减的就是它。
/* Ref、PoolManager和AutoreleasePool之间并无继承关系,
* 而AutoreleasePool中部分成员方法需要使用Ref的成员方法(例如AutoreleasePool::clear()使用Ref::release()),所以在此定义为友元类。
*/
friend class AutoreleasePool;
#if CC_ENABLE_SCRIPT_BINDING // 是否开启脚本绑定。开启后就可以使用脚本语言调用引擎提供的接口,开发游戏更加高效。
public:
/// object id, ScriptSupport need public _ID
unsigned int _ID;
/// Lua reference id
int _luaID;
/// scriptObject, support for swift
void* _scriptObject;
#endif
2、成员方法:
(1) Ref()
实现源码:
Ref::Ref()
: _referenceCount(1) // 对象被创建之时,引用计数初始化为1。
{
#if CC_ENABLE_SCRIPT_BINDING // 脚本绑定相关,暂不分析。
static unsigned int uObjectCount = 0;
_luaID = 0;
_ID = ++uObjectCount;
_scriptObject = nullptr;
#endif
#if CC_REF_LEAK_DETECTION // 内存泄漏检测相关,暂不分析。
trackRef(this);
#endif
}
关键点总结:
♂ 几乎所有的对象类都继承自Ref,所以Ref被创建可以理解为对象被创建。对象被创建之时当然引用计数为1,其他地方使用该对象时引用计数再++,不使用了则–。
(2) void retain()
void release()
这两个方法分别用于增加和减少对象的引用计数。
实例:
在对ActionManager的分析中有实例:http://www.cocoachina.com/bbs/read.php?tid-296371.html
实现源码:
void Ref::retain()
{
CCASSERT(_referenceCount > 0, "reference count should greater than 0"); // 只要对象存在,该对象的引用计数一定大于0,否则该对象就应该被delete了。
++_referenceCount; // 增加对象的引用计数。
}
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should greater than 0"); // 只要对象存在,该对象的引用计数一定大于0,否则该对象就应该被delete了。
--_referenceCount; // 减少对象的引用计数。
if (_referenceCount == 0) // 如果对象的引用计数为0。
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) // 调试的时候使用的,可以略过,不过其中举了几个关于内存管理的例子。
auto poolManager = PoolManager::getInstance(); // 获取PoolManager实例。
// 如果当前的AutoreleasePool未在AutoreleasePool::clear()的执行过程中,并且当前对象在AutoreleasePool中(即需要被自动释放)。
if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
{
// 当对象的引用计数为0,但对象还在AutoreleasePool中时就会进入这里。什么时候会出现这种情况?
// 当 'autorelease/release' 和 'new/retain' 没有成对使用时就会出现这种情况。
//
// Wrong usage (1):
//
// auto obj = Node::create(); // 此时obj的引用计数为1。并且在Node::create()内部已将new出来的Node对象放入AutoreleasePool中。
// obj->autorelease(); // 错误:autorelease()需要与retain()成对出现,如果想多次使用autorelease()请先obj->retain()。
//
// Wrong usage (2):
//
// auto obj = Node::create();
// obj->release(); // 错误:obj已经在Node::create()中被放入AutoreleasePool,无需显式的release()。
//
// Correct usage (1):
//
// auto obj = Node::create(); // 在Node::create()中new Node(),并且对new出的对象调用autorelease()。
// |- new Node();
// |- autorelease();
//
// obj->retain();
// obj->autorelease(); // 这里使用autorelease()会在每帧结束的时候判断该对象是否可以被delete。
//
// Correct usage (2):
//
// auto obj = Node::create();
// obj->retain();
// obj->release(); // 这里使用release()会马上判断该对象是否可以被delete。
CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool."); // 引用计数为0,并且还在AutoreleasePool中,要报错。
}
#endif
#if CC_REF_LEAK_DETECTION
untrackRef(this);
#endif
delete this; // 该对象的引用计数为0,没人使用了,可以被delete了。
}
}
关键点总结:
♂ 实现源码很简单,核心只有三行代码,增减引用计数以及delete自己。
♂ 当程序的某部分使用对象时,为防止对象被意外的delete,程序会调用该方法以声明“我正用着呢”。这样在这段程序自己调用release()之前(autorelease()的情况除外,在之后讨论),该段程序始终占用对象的1个引用计数,即对象的引用计数始终会大于0,那么对象就不会被释放了。当该段程序调用release()释放对象的所有权后,如果没有其他程序段使用该对象,那么对象的引用计数变为0,在release()中delete自己。
(3) Ref* autorelease()
该方法用于将对象放入AutoreleasePool中。
实例:
上面Ref::release()中有实例。
实现源码:
Ref* Ref::autorelease()
{
/* 这里用到了PoolManager和AutoreleasePool。
* PoolManager::getInstance():获取引擎中的PoolManager的实例。
* ->getCurrentPool():获取当前使用的AutoreleasePool。
* ->addObject(this):将当前对象放入AutoreleasePool中。
*/
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
关键点总结:
♂ 被放入AutoreleasePool中的对象会在每帧结束时由AutoreleasePool::clear()对其执行release(),详见AutoreleasePool::clear()的分析。
(4) unsigned int getReferenceCount() const
该方法用于获取当前对象的引用计数。
实现源码:
unsigned int Ref::getReferenceCount() const
{
return _referenceCount; // 源码很简单,直接返回对象的引用计数的值。
}
PoolManager:
1、成员变量:
public:
/* Ref、PoolManager和AutoreleasePool之间并无继承关系,
* 而AutoreleasePool中部分成员方法需要使用PoolManager的私有成员方法(例如AutoreleasePool的构造和析构函数使用PoolManager的push()和pop()),所以在此定义为友元类。
/
friend class AutoreleasePool;
private:
static PoolManager s_singleInstance; // PoolManager单例类指针,指向引擎创建的PoolManager。
std::vector<AutoreleasePool*> _releasePoolStack; // 用于存放AutoreleasePool的容器。PoolManager可以管理多个AutoreleasePool,但只有容器中最后一个AutoreleasePool作为当前有效的AutoreleasePool。
2、成员方法:
(1) PoolManager()
~PoolManager()
实现源码:
PoolManager::PoolManager()
{
_releasePoolStack.reserve(10); // 为容器分配10个元素的空间。
}
PoolManager::~PoolManager()
{
CCLOGINFO("deallocing PoolManager: %p", this);
while (!_releasePoolStack.empty()) // 如果容器不为空。
{
AutoreleasePool* pool = _releasePoolStack.back(); // 从最后一个AutoreleasePool开始。
delete pool; // 依次释放所有的AutoreleasePool。
}
}
(2) void push(AutoreleasePool *pool)
void pop()
这两个方法分别用于添加一个新的AutoreleasePool作为当前有效的AutoreleasePool以及删除当前有效的AutoreleasePool。
pool:待添加的AutoreleasePool。
实例:
在AutoreleasePool的构造以及析构函数中使用。
实现源码:
void PoolManager::push(AutoreleasePool *pool)
{
// 将新的AutoreleasePool放入容器的最后。(在PoolManager::getCurrentPool()中可以看到,容器中最后一个AutoreleasePool就是当前有效的AutoreleasePool)
_releasePoolStack.push_back(pool);
}
void PoolManager::pop()
{
CC_ASSERT(!_releasePoolStack.empty()); // 如果容器为空,那还pop()什么,要报错。
_releasePoolStack.pop_back(); // 删除容器中最后一个AutoreleasePool(即当前有效的AutoreleasePool)。
}
(3) static PoolManager* getInstance()
static void destroyInstance()
这两个方法分别用于获取引擎为单例类PoolManager分配的实例以及销毁该实例。
实现源码:
PoolManager* PoolManager::s_singleInstance = nullptr; // 单例类PoolManager实例指针初始化。
PoolManager* PoolManager::getInstance()
{
if (s_singleInstance == nullptr) // 如果引擎还未创建PoolManager。
{
s_singleInstance = new (std::nothrow) PoolManager(); // 创建PoolManager。
/* 添加第一个AutoreleasePool(引擎默认添加)。
* 由于PoolManager为单例类,那么在AutoreleasePool构造函数内部可以获取到PoolManager的实例,在其中直接就将自己push()进PoolManager了。
* 所以这里无需接收new AutoreleasePool的返回。
*/
new AutoreleasePool("cocos2d autorelease pool");
}
return s_singleInstance; // 返回PoolManager实例。
}
void PoolManager::destroyInstance()
{
delete s_singleInstance; // 直接delete实例。
难道不加个非空判断?
s_singleInstance = nullptr; // 释放了实例所占的内存空间,单例类指针置空。
}
(4) AutoreleasePool *getCurrentPool() const
该方法用于获取当前有效的AutoreleasePool。
实现源码:
AutoreleasePool* PoolManager::getCurrentPool() const
{
// 返回容器中的最后一个元素。这里可以看到容器中最后一个AutoreleasePool是当前有效的AutoreleasePool,上面PoolManager::push()中也可以看到新添加的AutoreleasePool也是放到容器的最后的。
return _releasePoolStack.back();
}
(5) bool isObjectInPools(Ref* obj) const
该方法用于判断传入的对象是否在某个(所有管理的AutoreleasePool)AutoreleasePool之中。
obj:某个对象的实例。
实现源码:
bool PoolManager::isObjectInPools(Ref* obj) const
{
for (const auto& pool : _releasePoolStack) // 遍历PoolManager中管理AutoreleasePool的容器中的每个AutoreleasePool。
{
if (pool->contains(obj)) // 对象是否在这个AutoreleasePool中。
return true;
}
return false;
}
关键点总结:
♂ 目前这个方法只在Ref::release()中做调试使用。如果实际使用的话,觉得应该有个PoolManager::isObjectInPool(),只查对象是否在当前使用的AutoreleasePool中。
AutoreleasePool:
1、成员变量:
private:
std::vector<Ref*> _managedObjectArray; // 存储需要被自动释放的对象的容器。
std::string _name; // AutoreleasePool的名字,调试时使用。
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
/**
* The flag for checking whether the pool is doing clear operation.
*/
bool _isClearing; // 调试时使用。标识AutoreleasePool::clear()正在执行中。
#endif
2、成员方法:
(1) AutoreleasePool()
AutoreleasePool(const std::string &name)
~AutoreleasePool()
实现源码:
AutoreleasePool::AutoreleasePool()
: _name("") // AutoreleasePool的名字初始化为空。
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
, _isClearing(false) // AutoreleasePool初始不在clear()中。
#endif
{
_managedObjectArray.reserve(150); // 初始化150个元素的空间,同一帧可存储150个需要自动释放的对象。
PoolManager::getInstance()->push(this); // 将这个新的AutoreleasePool交给PoolManager管理(实际上是放到了PoolManager::_releasePoolStack的最后一个元素的位置)。
}
AutoreleasePool::AutoreleasePool(const std::string &name)
: _name(name) // 与上边不带参数的构造函数区别只在这里,初始化了一个AutoreleasePool的名字,调试时使用(AutoreleasePool::dump())。
#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(); // 调用自己的clear(),清理当前AutoreleasePool中存储的需要自动释放的对象。
PoolManager::getInstance()->pop(); // 将当前AutoreleasePool从PoolManager中移除。
}
(2) void addObject(Ref *object)
该方法用于将待自动释放的对象放入AutoreleasePool中。
object:需要被自动释放的对象。
实例:
上面Ref::release()中有实例。
实现源码:
void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object); // 很简单,就是将对象push入AutoreleasePool::_managedObjectArray容器中。
}
(3) void clear()
bool isClearing() const
这两个方法分别用于实现对象的自动释放功能以及标识AutoreleasePool::clear()是否在执行过程中。要真正实现自动释放功能还需要引擎mainloop()中一段代码的配合,详见下面“实例”中的描述。
实例:
PoolManager::getInstance()->getCurrentPool()->clear();
这段代码出现在DisplayLinkDirector::mainLoop(),在引擎每一帧绘制完屏幕后都会执行。引擎在每一帧结束之际,都会调用当前AutoreleasePool的clear(),清理AutoreleasePool中存放的对象,完成它们的自动释放。
实现源码:
void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = true; // 调试时使用,标识AutoreleasePool在clear的执行过程中。
#endif
for (const auto &obj : _managedObjectArray) // 遍历AutoreleasePool中的每个对象。
{
obj->release(); // 减少它们的引用计数。
}
_managedObjectArray.clear(); // 清空AutoreleasePool中存储的对象(否则每一帧结束,存储在其中的对象都会被release()一次)。
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = false; // 调试时使用,标识AutoreleasePool的clear过程结束。
#endif
}
bool isClearing() const { return _isClearing; // 直接返回标志的值。 };
关键点总结:
♂ clear()方法是AutoreleasePool的核心方法。mainloop()中调用AutoreleasePool中每个对象的release()方法释放它们所占用的引用计数,如果没有其它程序段占用该对象,则该对象的引用计数为0,在release()中delete自己,达到了自动释放的效果。
(4) bool contains(Ref* object) const
该方法用于判断给出的对象是否在当前的AutoreleasePool中。
实现源码:
bool AutoreleasePool::contains(Ref* object) const
{
for (const auto& obj : _managedObjectArray) // 遍历AutoreleasePool中的每个对象。
{
if (obj == object) // 如果是给出的对象。
return true;
}
return false;
}
(5) void dump()
该方法用于打印AutoreleasePool中管理的对象的一些详细信息,调试时使用。
实现源码:
void AutoreleasePool::dump()
{
CCLOG("autorelease pool: %s, number of managed object %d\n", _name.c_str(), static_cast<int>(_managedObjectArray.size())); // 打印出AutoreleasePool的名字以及存储了多少个对象。
CCLOG("%20s%20s%20s", "Object pointer", "Object id", "reference count"); // 格式化打印标题。
for (const auto &obj : _managedObjectArray) // 遍历出AutoreleasePool中的所有对象。
{
CC_UNUSED_PARAM(obj);
CCLOG("%20p%20u\n", obj, obj->getReferenceCount()); // 打印该对象的存放地址以及其引用计数。
}
}
总述:
1、当对象被创建之时,其引用计数为1,表示它自己对自己申请的这块内存有所有权。
2、使用对象的create()方法创建的对象(比如auto myMoveBy = MoveBy::create(…)),在create()内部已经将对象放入AutoreleasePool,不再使用对象时无需显式的release()。
3、在非创建对象的代码段或是使用create()创建的对象,使用时为防止对象被意外的delete(一帧结束,但对象还未使用完),需要显式的调用retain()。使用完成后一定要显式的调用release()或者autorelease(),否则会造成内存泄漏。
4、对象的自动释放功能通过AutoreleasePool实现。引擎中可同时存在多个AutoreleasePool,由PoolManager管理,但只有最后交给PoolManager管理的AutoreleasePool为当前有效的AutoreleasePool。引擎的mainloop()在每一帧结束之际会对AutoreleasePool中存储的每一个对象调用release(),这样如果没有其他代码段使用该对象,该对象的引用计数会减为0,对象所占的内存空间被释放;如果有其他代码段使用该对象,该对象的引用计数至少为1,对象不会被释放,这样也不会对使用它的代码段有任何影响。
5、在创建对象的代码段,当使用完对象后,如果确定其它代码段不会使用该对象时建议显式的调用release()释放对象,如果不确定则建议显式的调用autorelease()交由引用计数机制管理。
6、new/retain()与autorelease()/release()使用时要成对出现,否则容易造成内存泄漏。