cocos2d-x 内存管理机制

一、cc.Node

Node类是绝大部分类的父类,如Scene、Layer,Sprite,Button等,要理解cocos的内存管理,首先的是从Node类源码开始入手,抽丝剥茧,一步一步去挖掘

Node * Node::create()
{
    Node * ret = new (std::nothrow) Node();
    if (ret && ret->init())
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(ret);
    }
    return ret;
}
void Node::addChild(Node* child, int localZOrder, const std::string &name)
{
    CCASSERT(child != nullptr, "Argument must be non-nil");
    CCASSERT(child->_parent == nullptr, "child already added. It can't be added again");
    addChildHelper(child, localZOrder, INVALID_TAG, name, false);
}

void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag)
{
    if (_children.empty())
    {
        this->childrenAlloc();
    }
    this->insertChild(child, localZOrder);
    ......
}
void Node::childrenAlloc()
{
    _children.reserve(4);
}
// helper used by reorderChild & add
void Node::insertChild(Node* child, int z)
{
    _transformUpdated = true;
    _reorderChildDirty = true;
    _children.pushBack(child);
    child->_localZOrder = z;
}

void Node::removeFromParent()
{
    this->removeFromParentAndCleanup(true);
}

void Node::removeFromParentAndCleanup(bool cleanup)
{
    if (_parent != nullptr)
    {
        _parent->removeChild(this,cleanup);
    } 
}

void Node::removeChild(Node* child, bool cleanup /* = true */)
{
    // explicit nil handling
    if (_children.empty())
    {
        return;
    }

    ssize_t index = _children.getIndex(child);
    if( index != CC_INVALID_INDEX )
        this->detachChild( child, index, cleanup );
}

void Node::removeChild(Node* child, bool cleanup /* = true */)
{
    // explicit nil handling
    if (_children.empty())
    {
        return;
    }

    ssize_t index = _children.getIndex(child);
    if( index != CC_INVALID_INDEX )
        this->detachChild( child, index, cleanup );
}

void Node::detachChild(Node *child, ssize_t childIndex, bool doCleanup)
{
    // IMPORTANT:
    //  -1st do onExit
    //  -2nd cleanup
    if (_running)
    {
        child->onExitTransitionDidStart();
        child->onExit();
    }

    // If you don't do cleanup, the child's actions will not get removed and the
    // its scheduledSelectors_ dict will not get released!
    if (doCleanup)
    {
        child->cleanup();
    }

    // set parent nil at the end
    child->setParent(nullptr);

    _children.erase(childIndex);
}

1、首先看create函数,这就函数是创建节点,这里需要注意是就是autorelease()这个方法,这个方法是干什么的呢?疑问1我们继续往下找,这个方法是Node父类的,顺着这条线,我们找到了Ref这个类,Ref是啥呢?这里先保留这个疑问2,等下在讲,现在继续看addChild()函数

2、再看addChild()->addChildHelper()->insertChild()->_children.pushBack(child)这个函数就是把节点pushBack到_children这个容器中去,然后设置name、tag,localZoOder,_children是什么呢?在Node中找,最后找到_children是一个Vector,装子节点的,每个Node上都有一个_children。这里可以看到pushBack这里调用了retain(),函数retain是啥呢?疑问3

3、最后看removeFromParent()->removeFromParentAndCleanup()->removeChild()->detachChild()->_children.erase(childIndex),这个函数就是把节点从_children中erase(),这里可以看到erase这里调用了release(),函数release()是啥呢?疑问4

Vector<Node*> _children;        ///< array of children nodes

void pushBack(T object)
{
   CCASSERT(object != nullptr, "The object should not be nullptr");
   _data.push_back( object );
   object->retain();
}
 
iterator erase(ssize_t index)
{
   CCASSERT(!_data.empty() && index >=0 && index < size(), "Invalid index!");
   auto it = std::next( begin(), index );
   (*it)->release();
       return _data.erase(it);
}

二、Ref

Ref到底是啥呢?还是从源码入手,我们找到Ref的源码,主要包括如下的函数:

class CC_DLL Ref
{
public:
    void retain();                             // ++_referenceCount
    void release();                            // --_referenceCount
    Ref* autorelease();                        // 添加到AutoReleasePool
    unsigned int getReferenceCount() const;    // 获取引用次数

protected:
    unsigned int _referenceCount;              // 记录引用次数
};

NS_CC_END

#endif // __BASE_CCREF_H__
Ref::Ref()
: _referenceCount(1) // 当创建Ref时,它的引用计数为1
{
#if CC_ENABLE_SCRIPT_BINDING
    static unsigned int uObjectCount = 0;
    _luaID = 0;
    _ID = ++uObjectCount;
#endif
    
#if CC_REF_LEAK_DETECTION
    trackRef(this);
#endif
}

void Ref::retain()
{
    CCASSERT(_referenceCount > 0, "reference count should greater than 0");
    ++_referenceCount;
}

void Ref::release()
{
    CCASSERT(_referenceCount > 0, "reference count should 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_REF_LEAK_DETECTION
        untrackRef(this);
#endif
        delete this;
    }
}
Ref* Ref::autorelease()
{
    PoolManager::getInstance()->getCurrentPool()->addObject(this);
    return this;
}

如何理解这个段代码呢?由上面代码可知道,cocos2d采用了引用计数的方法来进行来对对象进行  内存管理,(疑问2回答)Ref是cocos的内存管理类,cocos2d几乎所有的对象节点继承于Ref

1、首先看构造函数,当创建对象时,它的引用计数赋值为1(重点)

2、(疑问3回答)然后看retain函数,这个接口作用就是_referenceCount+1

3、(疑问4回答)接着看release函数,这个接口作用就是_referenceCount-1,当_referenceCount==0的时候,对象会被回收释放(重点)

4、再看autorelease()函数,这个函数有什么用呢?留疑1问接着往下看,这个函数里面出现了PoolManager这个单例,回到Ref.h,我们找到了AutoreleasePool这个类

friend class AutoreleasePool;

三、AutoreleasePool和PoolManager

void AutoreleasePool::addObject(Ref* object)
{
    _managedObjectArray.push_back(object);
}

void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = true;
#endif
    std::vector<Ref*> releasings;
    //将_managedObjectArray的数据和releasings交换,
    //以达到清理上一帧的_managedObjectArray这个vector,
    //来确保已经被release过的节点已经被踢出releasePool,具体请参考
    //c++ vector.swap原理
    releasings.swap(_managedObjectArray);
    // 遍历所有对象,进行引用计数-1,为0的销毁对象
    for (const auto &obj : releasings)
    {
        obj->release();
    }
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = false;
#endif
}

AutoreleasePool* PoolManager::getCurrentPool() const
{
    return _releasePoolStack.back();
}

AutoReleasePool,解释过来就是自动释放池,是来存放Node:create() 的一个容器。这里终于可以解释疑问1了,autorelease()就是把对象加入到了自动释放对象池中,这里只是添加进去了,哪里调用了AutoreleasePool::clear()函数呢?继续往下找,在CCDirector单例的manLoop()中找到了这个函数的调用:

void DisplayLinkDirector::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();
    }
    else if (! _invalid)
    {
        drawScene();
     
        // release the objects
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

coocs2d每一帧都会对当前AutoreleasePool进行clear(),把引用计数为0的对象清理。这里有几点需要解释一下

1、如果一个对象在创建后,在当前帧没有调用addChild(), 对象就会被销毁掉,所以对象一定要在当前帧结束前调用

2、对象在addChild()后_referenceCount由1变成2了(在Node和Ref有讲到过),然后通过AutoreleasePool的clear(),又从_referenceCount=2变为_referenceCount=1,clear()中使用swap()方法保证了节点对象在当前的AutoreleasePool中仅存在一帧的时间,避免重复遍历

3、调用removeFromParent来删除对象,引用计数由1变为0,对象会被释放

四、总结

cocos2dx的内存官方分为两种方式

1、手动方式:对象创建时_referenceCount赋值为1,调用retain(),_referenceCount+1,调用release(),_referenceCount-1,_referenceCount == 0时会被引擎回收掉释放内存

2、自动方式:在create()一个对象时,会调用autorelease()方法把该对象加入AutoreleasePool,在 mainLoop()方法中会调用当前AutoreleasePool中的clear()方法,clear()方法会遍历移除所有与该AutoreleasePool中的对象,并调用一次对象release()方法,如果这个对象_referenceCount == 0就会被引擎回收掉释放内存

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值