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

发表于2017-12-21
评论0 1.3k浏览

1 目的

虽然目前手机的内存容量相比较以前有了大幅度的提升,但是面对众多游戏应用等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(构造函数初始化_referenceCount为1)
    */
    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/retain和autorelease/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来维护对象的创建和释放。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引