cocos2d-x 内存管理

环境: cocos 3.10 Xcode

 

C++ 内存管理简介

分配方式:

1. 静态分配,内存在编译的时候就已经分配好,该内存在程序运行期间都存在。比如:全局变量,static静态变量等。

2. 栈分配,在函数内部的局部变量上,函数执行结束后自动释放。效率高,但分配容量有限。

3. 堆分配,又称动态内存分配,使用new创建,delete销毁。使用灵活,但问题很多。

常见问题:

1. 创建失败,没有添加if判定是否不为NULL,就直接使用

2. 创建成功后,没有初始化

3. 销毁后,没有置为NULL,导致了野指针;或者根本就没释放,导致内存泄露

4. 使用越界,在数组中尤为突出

5. 重复释放

 

cocos2d-x 内存管理

1. Ref 

cocos2d-x中的所有对象几乎都继承于Ref基类。Ref的基类的主要就是对对象进行引用计数管理

class CC_DLL Ref:
{
public:
    void retain();                    // 增加引用计数
    void release();                   // 减少引用计数,引用计数为0时进行释放
    Ref* autorelease();               // 添加自动缓存池
    unsigned int getReferenceCount(); // 获取对象引用计数
protected:
    Ref();                            // 构造函数设为protected,保证可被继承且智能子类实例化                       
protected:
    unsigned int _referenceCount;     // 引用计数数目
    friend class AutoreleasePool;     // 自动释放池
}  

retain()release() 没有作任何特别事情,仅仅是负责对象的引用计数增加/减少而已。

Ref::Ref(): _referenceCount(1) // new一个对象时,引用计数加1
{
}

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

void Ref::release()
{
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
    --_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

#if CC_REF_LEAK_DETECTION
        untrackRef(this);
#endif
        delete this;
    }
}

Ref* Ref::autorelease()
{
    // 将对象添加到自动释放池管理器中
    PoolManager::getInstance()->getCurrentPool()->addObject(this);
    return this;
}

假设我们使用new创建一个对象,简单的示例:

auto node = new Node();         // 引用计数为1
addChild(node);                 // 引用计数为2
node->removeFromParent();       // 引用计数为1
node->release();                // 引用计数为0,释放对象

如果忘记使用release, 就会导致内存泄漏。为此,cocos添加了autorelease帮助我们自动管理内存。

// 通过autorelease()方法将对象添加到AutoreleasePool自动释放池中
Ref* Ref::autorelease()
{
    PoolManager::getInstance()->getCurrentPool()->addObject(this);
    return this;
}

cocos2d-x在每帧结束时都会清理AutoreleasePool自动释放池中的对象。主要通过mainLoop进行刷新,代码如下:

// widnows中使用DisplayLinkDirector, 继承于Director,用于同步Timer,刷新屏幕
void Director::mainLoop()
{
    if (! _invalid)
    {
        drawScene();
     
        // 清理引用计数为1的
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

在每帧结束后,都会调用AutoreleasePool下的clear进行引用计数减1的操作,代码如下:

void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = true;
#endif
    std::vector<Ref*> releasings;
    releasings.swap(_managedObjectArray);
    for (const auto &obj : releasings)
    {
        obj->release();    // 引用计数 -1
    }
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = false;
#endif
}

这样设计的话,若每帧结束后,对象创建且未被使用,引用计数将从1变为0,对象将被销毁,比如:

auto node = new Node();         // 引用计数1
node->autorelease();           // 加入自动释放池

若在第一帧结束前,被addChild到屏幕中,引用计数从1变为了2,对象将不被销毁。比如:

auto node = new Node();         // 引用计数1
node->autorelease();            // 加入自动释放池
addChild(node);                 // 引用计数2 

 

cocos为了简化上述的使用,提供了静态方法:create(),以Node::create()为例:

Node * Node::create()
{
    Node * ret = new (std::nothrow) Node();
    // 检测对象是否创建成功并初始化
    if (ret && ret->init())
    {
        ret->autorelease();         // 添加到自动释放池中
    }
    else
    {
        CC_SAFE_DELETE(ret);
    }
    return ret;
}    

该create又被称为“二次构建”,意为创建对象时,构造函数仅用于分配内存,而初始化借助于init来完成。

这样做的原因是cocos2d-x是从cocos2d-iphone移植而来,一方面是兼容,一方面是防止程序忘记初始化的操作等。

 

2.AutorelasePool 和 PoolManager

关于autorelease的实现主要通过AutoreleasePool释放池其代码为:

AutoreleasePool::AutoreleasePool()
: _name("")
{
    // 设置容器大小
    _managedObjectArray.reserve(150);     
    PoolManager::getInstance()->push(this);
}

AutoreleasePool::AutoreleasePool(const std::string &name)
: _name(name)
{
    _managedObjectArray.reserve(150);
    PoolManager::getInstance()->push(this);
}

AutoreleasePool::~AutoreleasePool()
{
    // 清理释放池
    clear();
    // 将释放池从管理器中移除
    PoolManager::getInstance()->pop();
}

void AutoreleasePool::addObject(Ref* object)
{
    // 添加对象到释放池中
    _managedObjectArray.push_back(object);
}

void AutoreleasePool::clear()
{
    // 循环遍历对象的release, 当引用技术为0的,进行删除
    std::vector<Ref*> releasings;
    releasings.swap(_managedObjectArray);
    for (const auto &obj : releasings)
    {
        obj->release();    // 
    }
}

AutoreleasePool可有多个,为了管理,cocos创建了PoolManager管理类。其主要代码如下:

// PoolManager.h
class CC_DLL PoolManager
{
public:
    static PoolManager* getInstance();
    // 获取当前的自动释放池
    AutoreleasePool *getCurrentPool() const;
    // 判定对象池管理器中是否存在该对象
    bool isObjectInPools(Ref* obj) const;
    
private:
    // 压入和弹出释放池中
    void push(AutoreleasePool *pool);
    void pop();
};

PoolManager主要的功能就是管理释放池相关.

 ...

posted @ 2020-03-22 21:56  Code~  阅读(601)  评论(0编辑  收藏  举报