cocos2d-x v3.3 Ref, PoolManager and AutoreleasePool

引擎采用引用计数的机制管理内存,引用计数的最核心思想就是标识一块内存有多少人在使用它(所有权),当没有人使用时它就被释放。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()使用时要成对出现,否则容易造成内存泄漏。

C++允许delete一个nullptr。所以可以不判断。

谢谢!学习了。

自己用代码试了下,C和C++对于释放(或是重复释放)一个空指针都不会报错。

C:
char *p = NULL;
free§;
free§;
……

C++:
char *p = nullptr;
delete p;
delete p;
……

而C和C++对于重复释放一块有效的内存的情况均会抛出异常。

C:
char *p = (char *)malloc(1);
free§;
free§;
……

C++:
A *p = new A();
delete p;
delete p;
……

我的游戏是在安卓系统上面跑的,而cocos游戏逻辑是在安卓的native层实现的,如果cocos依附的Activity被destroy了,那么cocos的那些申请到的内存如Scene、Layer、Sprite之类的对象什么时候被释放?