Cocos2d-x C++ 内存管理

简述

cocos2dx使用引用计数法(计数变量为基类Ref的成员变量_referenceCount)自动管理内存,自动回收池AutoReleasePool由PoolManager进行管理,每当使用create函数创建对象时,对象的_referenceCount为1并加入到自动回收池中,之后引用此对象/取消引用会同步加/减计数变量。当每帧事件处理结束时,自动回收池将池中的对象的计数减1并清空回收池,此时若某对象计数为0,则说明这个对象没有被任何对象引用,则会释放这个对象。若这帧之后某个被引用的对象被取消引用后计数为0,则会立刻释放这个对象。

源码分析

创建节点时

以Node为例:

Node * Node::create(){
    //std::nothrow代表当内存不足以new对象时不抛出异常而是使返回值变为nullptr
    Node * ret = new (std::nothrow) Node();
    //如果创建对象成功并且初始化成功
    if (ret && ret->init()){
        //将节点放入自动回收池
        ret->autorelease();
    } else {
        //如果创建失败,删除节点
        CC_SAFE_DELETE(ret);
    }
    return ret;
}

当调用Node::create()时,在new Node()的过程中首先调用基类Ref的构造函数,初始化计数变量为1:

//_referenceCount是内存管理中引用计数法的计数变量,通过此变量记录该对象被引用的个数
Ref::Ref() : _referenceCount(1) //...
{
//...
}

,随后调用autorelease函数将节点自身放入自动回收池:

Ref* Ref::autorelease()
{
    PoolManager::getInstance()->getCurrentPool()->addObject(this);
    return this;
}

引用/取消引用节点时

当执行addChildren、addAction、setScheduler等函数时,最终都会调用被引用对象的retain函数,以增加计数:

//addChild->addChildHelper->insertChild
void Node::insertChild(Node* child, int z)
{
    //...
    _children.pushBack(child);
    //...
}
//这里的Vector是cocos根据STL的vector写的包装类,在加入对象\删除对象时会自动给对象计数
void pushBack(T object)
{
    CCASSERT(object != nullptr, "The object should not be nullptr");
    _data.push_back( object );
    //调用retain函数
    object->retain();
}
void Ref::retain()
{
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
    ++_referenceCount;
}

当执行removeChildren等函数时,最终都会调用被引用对象的release函数,以减少计数,同时如果计数为0,则会立即释放节点:

//removeChild->detachChild
void Node::detachChild(Node *child, ssize_t childIndex, bool doCleanup)
{
    //...
    _children.erase(childIndex);
}
iterator erase(ssize_t index)
{
    CCASSERT(!_data.empty() && index >=0 && index < size(), "Invalid index!");
    auto it = std::next( begin(), index );
    //调用release函数
    (*it)->release();
    return _data.erase(it);
}
void Ref::release()
{
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
    --_referenceCount;
    if (_referenceCount == 0)
    {
        //...
        delete this;
    }
}

自动回收

在每帧调用的Director::mainLoop函数中,在处理事件之后调用了PoolManager::getInstance()->getCurrentPool()->clear()来清理自动回收池的对象:

void Director::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();
    }
    else if (_restartDirectorInNextLoop)
    {
        _restartDirectorInNextLoop = false;
        restartDirector();
    }
    else if (! _invalid)
    {
        //每帧的事件在这里处理
        drawScene();
        //处理结束之后清理自动回收池
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

在clear函数中,将回收池中的所有对象依次调用release函数并清空回收池:

void AutoreleasePool::clear()
{
    //...
    std::vector<Ref*> releasings;
    //将两个数组的数据空间指针交换,即把_managedObjectArray数组置空
    releasings.swap(_managedObjectArray);
    for (const auto &obj : releasings)
    {
        //这里减的时刚创建对象时的那1个计数
        obj->release();
    }
    //...
}

也就是说,只有当前帧内创建的对象才会被放入自动回收池中(并在该帧清理结束时移出自动回收池),如果这个对象在创建的这帧内没有被任何引用,才会被自动回收池释放,这样保证剩下的对象都有其他对象引用。在之后的帧中,如果对象被取消引用导致计数为0,则会被取消引用时调用的release函数立刻释放(与自动回收池无关)。

内存泄漏检查

cocos2dx中存在一个宏定义CC_REF_LEAK_DETECTION,开启后可以使用Ref::printLeaks()函数打印当前时刻所有存在的以Ref为基类的对象类型与引用计数。

Ref::Ref()//...
{
//如果开启了cocos对象内存泄漏检查
#if CC_REF_LEAK_DETECTION
    //记录这个对象
    trackRef(this);
#endif
}
static void trackRef(Ref* ref)
{
    //在此函数内给变量加锁,防止多线程访问更改变量
    std::lock_guard<std::mutex> refLockGuard(__refMutex);
    CCASSERT(ref, "Invalid parameter, ref should not be null!");
    //将这个对象记录在数组中,这个数组保存了所有存在的对象
    __refAllocationList.push_back(ref);
}
void Ref::release()
{
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
    --_referenceCount;
    if (_referenceCount == 0)
    {
        //...
#if CC_REF_LEAK_DETECTION
        //取消记录对象
        untrackRef(this);
#endif
        delete this;
    }
}
Ref::~Ref()
{
//...
#if CC_REF_LEAK_DETECTION
    if (_referenceCount != 0)
        //疑问:为什么不统一在析构函数中调用
        untrackRef(this);
#endif
}
static void untrackRef(Ref* ref)
{
    std::lock_guard<std::mutex> 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);
}
void Ref::printLeaks()
{
    std::lock_guard<std::mutex> refLockGuard(__refMutex);
    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());
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值