cocos2dx源码分析之内存管理

本文详细解析了Cocos2dx框架中内存管理的具体实现方式,重点介绍了引用计数机制和AutoreleasePool的工作原理,以及如何正确使用retain和release方法确保资源的有效管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在cocos2dx框架中,我们似乎有一种感觉–不用手动释放内存,其实,这些都是框架帮我们实现好的

cocos2dx中采用了一种比较古老的方式—–引用计数的方式来实现内存的自动回收,如果是了解c++11中的std::shared_ptr的同学肯定不会觉得陌生,原理是类似的。

我们首先来明确一个问题,cocos2dx是在什么地方释放内存的呢?

我们来看源码:

CCDirector.cpp
//简化版
void DisplayLinkDirector::mainloop()
{
    ...
    drawScene();
    PoolManager::getInstance()->getCurrentPool()->clear();
    ...
}

这个是cocos2dx的主循环,我们不难看出,在每一帧绘制结束后,PoolManager就会进行一个clear()操作,这里,就是我们cocos2dx内存管理的入口了

那么这个PoolManager又是怎么实现的呢?

这个就要从我们Ref类讲起了

class CC_DLL Ref
{
    public:
         void retain();
         void release();
         Ref* autorelease();
         unsigned int getReferenceCount() const;
         ...
    protected:
        unsigned int _referenceCount;
        friend class AutoreleasePool;
}

根据我们上面主循环的代码PoolManager 应该是实现内存自动释放的关键

class CC_DLL PoolManager
{
    public:
        ...
        static PoolManager* getInstance();
        AutoreleasePool *getCurrentPool() const;
        friend class AutoreleasePool;
        ...
    private:
        std::vector<AutoreleasePool*> _releasePoolStack;
        void push(AutoreleasePool *pool);
        void pop();
}
...
AutoreleasePool* PoolManager::getCurrentPool() const
{
    return _releasePoolStack.back();
}

这是一个单例类,其中调用的getCurrentPool 方法返回的只是一个AutoreleasePool 的向量

并且这个AutoreleasePool 是他的友缘类,而且也是Ref 的友缘类

我们来看看AutoreleasePool 的实现

class CC_DLL AutoreleasePool
{
    public:
        void addObject(Ref *object);
        void clear();
        bool contains(Ref* object) const;
        void dump();
    private:
        std::vector<Ref*> _managedObjectArray;
        std::string _name;

}

void AutoreleasePool::clear()
{
...
    std::vector<Ref*> releasings;
    releasings.swap(_managedObjectArray);
    for (const auto &obj : releasings)
    {
        obj->release();
    }
...
}

Ref::relase的实现
void Ref::release()
{
...
    --_referenceCount;
    if (_referenceCount == 0)
    {
        delete this;
    }
...
}

大体上就是这样一个流程
流程

那么如何保留我们的实例不被释放呢?

很简单,使用我们的retain 方法,其实就是将引用计数加一

void Ref::retain()
{
...
    ++_referenceCount;
...
}

因为每次进行clear() 的时候,会把_managedObjectArray 清空
也就是说,每个继承自Ref 的类实例化后只会进行一次release,所以我们调用retain 将引用计数加一后就不用担心被释放的问题了

那么reatin 后的实例如何释放呢?

也很简单, 调用 autorelease 或者 release 即可

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

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

Autorelease 会将当前实例加入到待释放数组中,所以就可以实现自动释放了

还有一个问题,既然是引用计数,那么最开始的引用数是在哪里赋值的呢?

是在Ref 的构造函数中

Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
    ...
{
    ...
}

所以,我们在cocos2dx开发中,一般使用框架中的Vector
因为VectorpushBack() 的时候,会调用一次retain

官方文档上的一些话:

CCAutoreleasePool不能被开发者自己创建。Cocos2d-x会为我们每一个游戏创建一个自动释放池实例对象,游戏开发者不能新建自动释放池,仅仅需要专注于release/retain cocos2d::CCObject的对象。

CCAutoreleasePool不能被用在多线程中,所以假如你游戏需要网络线程,请仅仅在网络线程中接收数据,改变状态标志,不要这个线程里面调用cocos2d接口。下面就是原因:

CCAutoreleasePool的逻辑是,当你调用object->autorelease(),object就被放到自动释放池中。自动释放池能够帮助你保持这个object的生命周期,直到当前消息循环的结束。在这个消息循环的最后,假如这个object没有被其他类或容器retain过,那么它将自动释放掉。例如,layer->addChild(sprite),这个sprite增加到这个layer的子节点列表中,他的声明周期就会持续到这个layer释放的时候,而不会在当前消息循环的最后被释放掉。

这就是为什么你不能在网络线层中管理CCObject生命周期,因为在每一个UI线程的最后 ,自动释放对象将会被删除,所以当你调用这些被删掉的对象的时候,你就会遇到crash。

简而言之,这只有两种情况你需要调用release()方法

你new一个cocos2d::CCObject子类的对象,例如CCSprite,CCLayer等。

你得到cocos2d::CCObject子类对象的指针,然后在你的代码中调用过retain方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值