cocos2dx 4.0 内存管理分析

本文适用于cocos2dxV3.17.2 - V4.0版本 (2019.5至今)

核心基类-Ref

  • 在cocos/base cocos/2d 等核心类都是Ref的派生类,而Ref类正是充当了内存管理的角色
    在这里插入图片描述

  • Ref 定义

class CC_DLL Ref
{
public:
    void retain();
    void release();
    Ref* autorelease();

protected:
    Ref();

protected:
    unsigned int _referenceCount; // 引用计数
    friend class AutoreleasePool;
  • 当Ref 被创建的时候,对象引用计数为1
Ref::Ref()
: _referenceCount(1) 
}
  • 引用计数+1
void Ref::retain()
{
    ++_referenceCount;
}
  • 引用计数-1, 当引用计数为0时,delete 对象
void Ref::release()
{
    --_referenceCount;
    if (_referenceCount == 0)
    {
        delete this;
    }
}
  • 核心:autorelease,将自身添加至PoolManager当前自动释放池(AutoreleasePool),将自身交给PoolManager管理
Ref* Ref::autorelease()
{
    PoolManager::getInstance()->getCurrentPool()->addObject(this);
    return this;
}

自动释放池类-AutoreleasePool

  • AutoreleasePool 定义
class CC_DLL AutoreleasePool
{
public:
    AutoreleasePool(const std::string &name);
    ~AutoreleasePool();
    void addObject(Ref *object);
    void clear();
    
private:
    std::vector<Ref*> _managedObjectArray; // 保存池内管理的对象,(池全部对象)
    std::string _name; // 池的名字,cocos维护的池有一个名字,你可以用其他名字创建自己的池
  • AutoreleasePool 构造函数将为 _managedObjectArray 申请预留空间150 ,然后将自身push到PoolManager实例
AutoreleasePool::AutoreleasePool(const std::string &name)
: _name(name)
{
    _managedObjectArray.reserve(150);
    PoolManager::getInstance()->push(this);
}
  • 向池末增加对象
void AutoreleasePool::addObject(Ref* object)
{
    _managedObjectArray.push_back(object);
}
  • 池内全部对象都执行一次Ref::release(), 然后清空_managedObjectArray(也就清空了池)
void AutoreleasePool::clear()
{
    std::vector<Ref*> releasings; // 空vector
    releasings.swap(_managedObjectArray); // 互换后,_managedObjectArray就是空的vector
    for (const auto &obj : releasings)
    {
        obj->release();
    }
    // 局部变量releasings被销毁
}

自动释放池管理类-PoolManager

  • 该类的作用是管理AutoreleasePool
  • PoolManager 定义
class CC_DLL PoolManager
{
public:

    static PoolManager* getInstance();
    static void destroyInstance();
    AutoreleasePool *getCurrentPool() const;
    bool isObjectInPools(Ref* obj) const;  
    friend class AutoreleasePool;
private:
    PoolManager();
    ~PoolManager();
    void push(AutoreleasePool *pool);
    void pop();
    
    static PoolManager* s_singleInstance; // 单例
    std::vector<AutoreleasePool*> _releasePoolStack; // 用来保存被PoolManager管理的AutoreleasePool
  • 获取PoolManager实例对象
PoolManager* PoolManager::getInstance()
{
    if (s_singleInstance == nullptr)
    {
        s_singleInstance = new (std::nothrow) PoolManager(); // 实例化PoolManager单例
        new AutoreleasePool("cocos2d autorelease pool"); // 创建自动释放池AutoreleasePool,Application生命周期内cocos只维护该池
    }
    return s_singleInstance;
}
  • 销毁 PoolManager实例对象
  • 销毁时机是Application被销毁时,(调用Application基类 ApplicationProtocol::~ApplicationProtocol())
void PoolManager::destroyInstance()
{
    delete s_singleInstance;
    s_singleInstance = nullptr;
}
  • 获取当前AutoreleasePool
AutoreleasePool* PoolManager::getCurrentPool() const
{
    return _releasePoolStack.back(); // 读取Vector末尾值,除非用户有自定义AutoreleasePool,否则Vector只有唯一元素
}
  • PoolManager._releasePoolStack末尾增加池
  • PoolManager._releasePoolStack删除末尾池
void PoolManager::push(AutoreleasePool *pool)
{
    _releasePoolStack.push_back(pool);
}

void PoolManager::pop()
{
    _releasePoolStack.pop_back();
}

主循环

  • 在导演的主循环中,每帧(每此执行mainLoop)渲染场景后, 进行了一次AutoreleasePool::clear()
void Director::mainLoop()
{
	// 前面处理一些导演清空或重置的情况
	// 下面才是我们需要关注
    else if (! _invalid)
    {
        drawScene(); // 渲染场景
     
        // release the objects
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

小结

根据上文我们知道,创建Ref派生类实例对象的时候,构造函数会设置实例对象的引用计数为1;
而每帧渲染过后,PoolManager会执行AutoreleasePool::clear() ,先将池内对象引用计数-1, 然后清空池;
假设代码是这样写的:

auto obj= new Node(); // obj引用计数为1
while(1) // 主循环
{
	drawScene();
	PoolManager::getInstance()->getCurrentPool()->clear(); // obj引用计数将被减一,变为0,且被delete
}

也就是说上一帧New出来的对象,他在下一帧渲染后就会被销毁,他的生命周期只有这么短吗,如果这么短为什么我们看到游戏界面的UI能一直存在?
想知道问题的答案请往下阅读文章。

Node 类

  • Node保存子节点的成员变量:Vector<Node*> _children;
  • 凡是继承了Node的节点都有addChild、removeChild
// addChild 跟踪
void Node::addChild(Node* child, int localZOrder, const std::string &name)
{
    addChildHelper(child, localZOrder, INVALID_TAG, name, false); // Add1
}

void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag)
{
    this->insertChild(child, localZOrder); // Add2
    if (setTag)
        child->setTag(tag);
    else
        child->setName(name);    
    child->setParent(this);
}

void Node::insertChild(Node* child, int z)
{
    _children.pushBack(child); // Add3
}

// -------------------------------------

// removeChild 跟踪
void Node::removeChild(Node* child, bool cleanup /* = true */)
{    
    ssize_t index = _children.getIndex(child);
    if( index != CC_INVALID_INDEX )
        this->detachChild( child, index, cleanup ); // Remove1
}

void Node::detachChild(Node *child, ssize_t childIndex, bool doCleanup)
{
    child->setParent(nullptr);
    _children.erase(childIndex); // Remove1
}

Vector 类

  • Node::addChild >>> Vector::pushBack
void pushBack(T object)
{
    CCASSERT(object != nullptr, "The object should not be nullptr");
    _data.push_back( object );
    object->retain(); // Add Finish
}
  • Node::removeChild >>> Vector::pushBack
void pushBack(const Vector<T>& other)
{
    for(const auto &obj : other) {
        _data.push_back(obj);
        obj->retain(); // Remove Finish
    }
}
  • Vector析构函数
~Vector<T>()
{
    CCLOGINFO("In the destructor of Vector.");
    clear();
}

void clear()
{
     for( auto& it : _data) {
         it->release();	// 好好留意这句:当Vector析构的时候,会对所有元素进行release(对应本文最后一句)
     }
     _data.clear();
 }

小结

根据上文我们知道,创建Ref派生类实例对象子节点A的时候,构造函数会设置实例对象的引用计数为1;
然后父节点会使用addChild,将子节点A保存到自身的vector属性内,而且AddChild也会调用Ref::retain 使子节点A引用计数+1
而每帧渲染过后,PoolManager会执行AutoreleasePool::clear() ,先将池内对象引用计数-1, 然后清空池;
代码就变成了这样:

auto director = Director::getInstance(); // 单例
auto scene = WelcomeScene::createScene(); // createScene等同create, create调用了autorelease; scene引用计数为1
auto sprite = Sprite::create("HelloWorld.png"); // create 里调用了autorelease; sprite引用计数为1
scene.addChild(sprite) // sprite 引用计数为2

director->runWithScene(scene); // 同理通过director._scenesStack.pushBack(scene); 将scene引用+1,变为2

while(1) // director:: mainloop主循环
{
	drawScene();
	
	// sprite引用计数将被减1,变为1;
	// 而且清空了AutoreleasePool,sprite不再被PoolManager管理,即下一帧不会再减1;
	// 直到游戏执行了逻辑scene.removeChild(sprite)后,引用计数变为0,sprite才被delete
	// scene 类似于 sprite:Director::popToRootScene >>> director._scenesStack.popBack();
	PoolManager::getInstance()->getCurrentPool()->clear(); 
}

总结

  • Ref 是cocos 节点对象基类,当Ref对象构造后,引用计数为1
  • 使用Ref::retain() 使引用计数+1
  • 使用Ref::release() 使引用计数-1;当引用计数为0的时候,对象会被delete;
  • 使用Ref::autorelease() 将对象交给 PoolManager 管理
    • 每帧渲染后,PoolManager会清空池,而且会对清空前池内的所有对象进行Ref::release(),销毁无用对象;
    • 清空池后,PoolManager 管理的池是全新的,空的,直到下一帧有新的对象调用了Ref::autorelease();
  • addChild 将调用Ref::retain()
  • removeChild 将调用Ref::release()
obj = New Node(); // obj 引用计数为1
parent.addChild(obj) // obj 引用计数+1 变为2

// A帧
// obj 被PoolManager管理
drawScene();
PoolManager::getInstance()->getCurrentPool()->clear(); // obj 引用计数-1 变为1; PoolManager 管理的当前池被清空;

// A+1帧
// obj 已经没有被PoolManager管理
drawScene();
PoolManager::getInstance()->getCurrentPool()->clear();

// A++++++++...帧
parent.removeChild(obj) // obj 引用计数-1, 变为0 , 执行delete obj

用人话总结

  • cocos2dx采用引用计数来管理内存,对象每次retain时引用计数+1,每次release时引用计数-1,当引用计数为0时,delete释放该对象。
  • 继承于Ref的类在创建对象的时候都会适用自动引用计数方法autorelease(将引用计数初始化为1),将创建的对象放入内存管理池中。
  • 在下一帧场景渲染后就释放掉这个内存管理池,此时该被添加到内存管理池中的对象就会全部release一次(引用计数-1,引用计数为0就释放)。
  • 所以一个Ref派生类对象创建出来时,如果没有调用自身retain方法或者被其他对象管理(如addChild),就会在创建出来的下一帧释放掉。
  • addChild方法中其实也是使用retain方法,removeChild的时候适用release方法。
  • 父节点析构时也会调用子节点的release方法:node中保存子节点的数据结构为Vector<Node*> _children;在node析构后会调用_children的析构,在_children的析构函数中会遍历自己所有成员,并且调用release。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值