Cocos2d-x 3.x 内存管理机制

1 动机

虽然目前计算机内存已经非常便宜,但也不是说我们可以毫无节制的不恰当的使用。尤其是移动端的内存很吃紧,整个手机的APP共享那么点内存,说不定手机卡死或者运行的APP被强制退出。了解Cocos2d-x内存管理机制以更好地优化游戏资源,尽量降低不必要的内存开销。

2 引用计数的内存管理机制

Cocos2d-x采用引用计数管理内存,简单的说:在对象内部添加一个计数器,当外部引用增加时,引用计数加1,反之,外部引用计数消失时,引用计数减1,直到该对象的引用计数为0时,引擎会删除该对象。在Cocos2d-x中,关于内存管理的类有:

1.  Ref;

2.  AutoreleasePool;

3.  PoolManager.

2.1 Ref

类Ref几乎是Cocos2d-x中所有类的父类,Ref类提供了引用计数的功能。下面是Cocos2d-x3.x中Ref类头文件的定义:

class CC_DLL Ref

{

public:

    /**

    * 获取对象的所有权

    * 增加对象的引用计数

    */

    void retain();

   

    /**

    * 立即释放对象的所有权

    * 减少对象的引用计数,当对象引用计数减为0时,直接销毁对象

    */

    void release();

   

    /**

    * 自动释放对象的所有权

    * 将对象添加到自动释放池,并且减少对象的引用计数,当对象引用计数减为0时,直接销毁对象

    */

    Ref* autorelease();

   

    /**

    * 获取当前对象的引用计数

    * 对象被创建时引用计数为1(构造函数初始化_referenceCount1

    */

    unsigned int getReferenceCount() const;

 

protected:

    Ref();

public:

    virtual ~Ref();

 

protected:

    unsigned int _referenceCount;  // 引用计数

    friend class AutoreleasePool;

 

public:

    unsigned int _ID;

    int         _luaID;

};

 

这里尤其要说明release方法,该方法源码如下:

void Ref::release()

{

   CCASSERT(_referenceCount > 0, "reference count should be greaterthan 0");

    --_referenceCount;

 

    if (_referenceCount == 0)

    {

#if defined(COCOS2D_DEBUG) &&(COCOS2D_DEBUG > 0)

       auto poolManager = PoolManager::getInstance();

       if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))

       {

           // 这里是非常重要的一点,在我们使用Cocos2d-x中经常出错的地方

           // 当引用计数为0,同时这个对象还存在于autorelease池中的时候,就会出现一个断言错误

           // 可以想到,当这个对象引用计数为0时,就表示需要释放掉,如果它还在autorelease池中,

           // 当在autorelease池中再次被释放时,就会出现错误,这种错误是不了解Cocos2d-x内存管理的

           // 编程人员经常犯的错误。

           //

           // 出现这个错误的原因在于new/retainautorelease/release没有对应使用引起的

           CCASSERT(false, "The reference shouldn't be 0 because it is still in autoreleasepool.");

       }

#endif

    }

}

上面提到了,对于new和autorelease、retain和release需要匹配使用,否则就会出现断言错误,或者内存泄露,这非常类似于C++中new和delete的成对使用;在非Debug模式下,就可能直接闪退了。这就是为什么我们在使用create函数的时候,new成功以后,就顺便调用了autorelease,将该对象放入到自动释放池中;而当我们再次想获取该对象并使用该对象的时候,需要使用retain再次获得对象的所有权,当然在使用完成以后,应该记得调用release去手动完成释放工作。下面是Cocos2d-x中常见创建autorelease对象的方法:

 

 

Wrong usage (1):重复加入自动对象池

auto obj= Node::create();// 引用计数为1,autorelease对象,已经被加入自动释放池

obj->autorelease();//WRONG!!! 企图再次将obj加入自动释放池。Ifyou wish to invoke autorelease several times, you should retain `obj` first.

 

Wrong usage (2):重复释放对象

autoobj = Node::create();

obj->release();   // WRONG!!! obj is an autorelease Ref, itwill be released when clearing current pool.

 

Correct usage(1): 成对使用new和autorelease

autoobj = Node::create();

|- new Node();     // `new` is the pair of the `autorelease`of next line

|- autorelease();  // The pair of `new Node`.

obj->retain();

obj->autorelease();  // This `autorelease` is the pair of `retain`。

 

Correct usage(2): 成对使用retain和release

autoobj = Node::create();

obj->retain();

obj->release();   // This `release` is the pair of `retain` ofprevious line.

 

请记住:利用Cocos2d-x引擎的引用计数管理机制,我们可以不再向C++由程序员手动分配和释放内存。在Cocos2d-x中最常见的应用是:通过在类中加入CREATE_FUNC(className);来创建对象使代码简洁优雅,且内存管理得当。下面是CREATE_FUNC(className)宏的定义:

#define CREATE_FUNC(__TYPE__) \

static __TYPE__* create() \

{ \

   __TYPE__ *pRet = new__TYPE__(); \

   if (pRet && pRet->init())\

    {\

       pRet->autorelease(); \

       return pRet; \

    }\

   else \

    {\

       delete pRet; \

       pRet = NULL; \

       return NULL; \

    }\

}

在这里,new和autorelease成对调用,且调用了HelloWorldScene中的init方法。

在类中只需要加入一行代码CREATE_FUNC(className);即可让我们创建一个autorelease对象(自动释放对象)如此简单。

auto sprite = Sprite::create(“1.png”); 或者像这样

auto layer = HelloWorldScene::create();

 

上述代码不仅简洁优雅,而且维护的是一个自动释放的对象,降低开发复杂度,减少程序员手动管理内存出错的机会。

2.2 AutoreleasePool

Cocos2d-x还使用了AutoreleasePool(自动释放池)来管理内存。需要自动释放的对象通过autorelease()方法将其加入自动释放池中,然后自动释放池AutoreleasePool帮我们进行内存管理。其中,在CREATE_FUNC宏中,使用new动态分配一块内存并调用类构造函数创建了对象,然后又成对的使用了autorelease()方法把创建的对象放入自动释放池,这使得我们可以使用create方法就可以简洁的创建对象,而不需要像C++使用原始的方法(new和delete)创建对象和手动释放对象。下面是AutoreleasePool类头文件源码:

 

class CC_DLL AutoreleasePool

{

public:

    /**

     * 不能在堆上创建一个自动释放对象,在栈上创建

     * 在栈上创建自动释放对象决定了该对象会被自动释放

     */

    AutoreleasePool();

   

    /**

     * 创建一个带有指定名字的autorelease pool对象,这个名字对调试非常有用

     */

    AutoreleasePool(const std::string &name);

 

    ~AutoreleasePool();

 

    /**

     * 将指定的`自动释放对象`加入自动释放池

     * 同一对象可能加入到自动释放池中很多次(不过会断言错误啊)

     * 当自动释放池销毁时,它会调用同样次数的`Ref::release()` 来销毁对象

     */

    void addObject(Ref *object);

 

     /**

    * 清理自动释放池

    * 依次调用自动释放池中对象的release()函数

    */

    void clear();

   

#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG> 0)

    /**

     * 判断是否正在对自动释放池执行清理操作

     */

    bool isClearing() const { return _isClearing; };

#endif

   

    /**

     *判断自动释放池是否包含指定的Ref对象

     */

    bool contains(Ref* object) const;

 

    /**

     * 打印autorelease pool中所有的对象

     */

    void dump();

   

private:

    /**

     *所有对象都是使用std::vector存放

     * 即自动释放池维护了一个std::vector容器,它是用于存放自动对象的数据结构

     */

    std::vector<Ref*> _managedObjectArray;

    std::string _name;

   

#if defined(COCOS2D_DEBUG) &&(COCOS2D_DEBUG > 0)

    /**

     *  Theflag for checking whether the pool is doing `clear` operation.

     */

    bool _isClearing;

#endif

};

AutoreleasePool内部使用std::vector容器作为存储自动释放对象的数据结构。类中的实现很简单,就是将对象保存子啊std::vector中,释放AutoreleasePool时依次调用对应的release函数释放保存在std::vector中的对象,从而完成对象的自动释放。

2.3 PoolManager

既然有了AutoreleasePool来管理对象的自动释放,那么PoolManager是做什么的?看其字面意思是`池管理器`。观查AutoreleasePool的源码后发现,其构造和析构函数都使用了PoolManager,原来是把AutoreleasePool对象都放于PoolManager管理,并且该类为单例类。先看看AutoreleasePool中的构造和析构函数源码:

 

AutoreleasePool::AutoreleasePool()

: _name("")

#if defined(COCOS2D_DEBUG) &&(COCOS2D_DEBUG > 0)

, _isClearing(false)

#endif

{

    _managedObjectArray.reserve(150);

    PoolManager::getInstance()->push(this);

}

 

AutoreleasePool::AutoreleasePool(const std::string &name)

: _name(name)

#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG> 0)

, _isClearing(false)

#endif

{

    _managedObjectArray.reserve(150);

    PoolManager::getInstance()->push(this);

}

 

AutoreleasePool::~AutoreleasePool()

{

    CCLOGINFO("deallocing AutoreleasePool: %p", this);

    clear();

   

    PoolManager::getInstance()->pop();

}

 

既然发现了为何要这么设计,那么下面来学习PoolManager头文件的声明:

class CC_DLL PoolManager

{

public:

 

    CC_DEPRECATED_ATTRIBUTEstatic PoolManager* sharedPoolManager() { return getInstance(); }

    static PoolManager* getInstance();

   

    CC_DEPRECATED_ATTRIBUTEstatic void purgePoolManager() { destroyInstance(); }

    static void destroyInstance();

   

    /**

     *Get current auto release pool, there is at least one auto release pool thatcreated by engine.

     *You can create your own auto release pool at demand, which will be put intoauto release pool stack.

     */

    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;

};

用于管理AutoreleasePool对象的PoolManager关键是:std::vector容器,而该类中的push和pop方法其实是封装了C++中push_back和pop_back方法。

3 总结

Cocos2d-x引擎为我们提供了非常方便的内存管理机制,在很多情况下,比如:Scene、Layer、Sprite、Director等,我们只需要使用create方法来创建`自动释放`的对象,还有addChild方法也自动调用了retain,因此我们不需要笨拙的使用new和delete来维护对象的创建和释放。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值