Cocos 内存管理

来源:互联网 发布:自动刷弹幕软件 编辑:程序博客网 时间:2024/06/01 07:58

亘古不变的东西

到现在,内存已经非常便宜,但是也不是可以无限大的让你去使用,特别是在移动端,那么点内存,那么多 APP要抢着用,搞不好,你占的内存太多了,系统直接干掉你的APP,所以说了,我们又要老生常谈了——内存管理。总结COM开发的时候,分析过COM的 内存管理模式;总结Lua的时候,也分析了Lua的内存回收机制;前几天,还专门写了C++中的智能指针在内存使用方面的应用;可见,内存管理无论是语言 层面,还是类库层面,都有严格的标准和实施,对于Cocos2d-x来说,也是如此。那么在Cocos2d-x中,它是如何进行内存管理的呢?这篇文章, 我就来总结一下关于Cocos2d-x的内存管理方面的知识。让你轻松度过面试官的五指关(面试Cocos2d-x时,100%会问到的问题啊,上点心 吧)。

初窥Cocos2d-x内存管理

对于探究内存管理这种比较抽象的东西,最简单的方法就是通过代码来研究,首先通过创建一个简单的场景来看看Cocos2d-x在完成创建一个对象的时候,它都干了些什么。

创建一个Scene:

auto scene = Scene::create();

函数create是一个静态函数,看看create函数的源码:

Scene *Scene::create(){    Scene *ret = new Scene();    if (ret && ret->init())    {        ret->autorelease();        return ret;    }    else    {        CC_SAFE_DELETE(ret);        return nullptr;    }}

现在就涉及到了Cocos2d-x的内存管理相关的知识了。在Cocos2d-x中,关于对象的创建与初始化都是使用的new和init函数搭配的方式,这种方式叫做二段式创建,由于C++中,构造函数没有返回值,无法通过构造函数确定初始化的成功与失败,所以在Cocos2d-x中就大行其道的使用了这种二段式创建的方式,用起来还不错,以后在自己的项目中,也可以采用。

由于这种方式在Cocos2d-x中经常被使用,所以触控那帮家伙就搞了个宏:CREATE_FUNC。如果想让我们的类也使用这种二段式创建的方式,只需要在我们的类中加入以下代码:

CREATE_FUNC(classname);

同时,需要定义一个init函数,这就OK了。我们来看看这个宏:

#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; \    } \}

话说这些东西也都是基础的C++知识,没有多少需要说的了,当你看到代码中的ret->autorelease(),一脸茫然,是的,你已经看到了Cocos2d-x的内存管理的触角了。

ret->autorelease()是什么?当我使用create函数创建了场景以后,我并没有去delete,这也没有问题。问题就发生在这个autorelease的使用上。序幕说完了,让我们真正的开始Cocos2d-x的内存管理吧。

在Cocos2d-x中,关于内存管理的类有:

  • Ref类;
  • AutoreleasePool类;
  • PoolManager类。

Ref类几乎是Cocos2d-x中所有类的父类,它是Cocos2d-x中内存管理的最重要的一环;上面说的autorelease函数就Ref类的成员函数,Cocos2d-x中所有继承自Ref的类,都可以使用Cocos2d-x的内存管理。

AutoreleasePool类用来管理自动释放对象。

PoolManager用来管理所有的AutoreleasePool,这个类是使用单例模式实现的。

下面就通过对上述三个类的源码进行分析,看看Cocos2d-x到底是如何进行内存管理的。

Ref类

先来看看Ref类的定义,以下是Ref类的头文件定义:

class CC_DLL Ref{public:    /**    * 获取对象的所有权    * 增加对象的引用计数    */    void retain();    /**    * 立即释放对象的所有权    * 同时会减少对象的引用计数,当引用计数达到0时,直接销毁这个对象    */    void release();    /**    * 自动释放对象的所有权    * 将对象添加到自动释放池    * 当在下一帧开始前,当前的自动释放池会被回收掉,并且对自动释放池中的所有对象    * 执行一次release操作,当对象的引用计数为0时,对象会被释放掉。    */    Ref* autorelease();    /**    * 获得对象的当前引用计数    * 当创建对象的时候,引用计数为1    */    unsigned int getReferenceCount() const;};

对于release函数的实现,这里需要特别总结一下,先看看它的实现:

void Ref::release(){    CCASSERT(_referenceCount > 0, "reference count should greater than 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 autorelease pool.");        }#endif        delete this;    }}

上面也说道了,对于new和autorelease需要匹配使用,retain和release也需 要匹配使用,否则就会出现断言错误,或者内存泄露;在非Debug模式下,就可能直接闪退了。这就是为什么我们在使用create函数的时候,new成功 以后,就顺便调用了autorelease,将该对象放入到自动释放池中;而当我们再次想获取该对象并使用该对象的时候,需要使用retain再次获得该 对象的所有权,当然了,在使用完成以后,你应该记得调用release去手动完成释放工作,这是你的任务。例如以下代码:

auto obj = Scene::create();obj->autorelease(); // Error

这是错误的,在create中,在创建成功的情况下,已经将obj对象放到了autorelease pool中了;当你再次放入autorelease pool后,当销毁autorelease pool以后,就会出现两次销毁一个对象的情况,出现程序的crash。再例如以下代码也是错误的:

auto obj = Scene::create();obj->release(); // Error

当使用create函数创建对象以后,obj没有所有权,当再次调用release时,就会出现错误的对象释放。而正确的做法应该如下:

auto obj = Scene::create(); // 这里retain和release对应,release一个已经被autorelease过的对象(例如通过create函数构造的对象)必须先retainobj->retain();obj->release();

这引用计数,又让我想起了COM中的AddRef和Release。

AutoreleasePool类

AutoreleasePool类是Ref类的友元类,先来看看Autorelease类的声明。

class CC_DLL AutoreleasePool{public:    /**    * 不能在堆上创建AutoreleasePool对象,只能在栈上创建    * 这就决定过了,当出了对应的作用域,AutoreleasePool对象就会被自动释放,例如RAII技巧实现的    */    AutoreleasePool();    /**    * 创建一个带有指定名字的autorelease pool对象    * 对于调试来说,这个名字是非常有用的。    */    AutoreleasePool(const std::string &name);    ~AutoreleasePool();    /**    * 向autorelease pool中添加一个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<Ref*> _managedObjectArray;    std::string _name;#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)    /**    *  The flag for checking whether the pool is doing `clear` operation.    */    bool _isClearing;#endif};

对于AutoreleasePool类来说,它的实现很简单,就是将简单的将对象保存在一个std::vector中,在释放这个AutoreleasePool的时候,对保存在std::vector中的对象依次调用对应的release函数,从而完成对象的自动释放。

PoolManager类

这货又是干什么的?当我们在阅读AutoreleasePool的源码的时候,在它的构造函数中,你会发现如下代码:

AutoreleasePool::AutoreleasePool(): _name("")#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0), _isClearing(false)#endif{    _managedObjectArray.reserve(150);    PoolManager::getInstance()->push(this);}

在AutoreleasePool的析构函数中,又有如下代码:

AutoreleasePool::~AutoreleasePool(){    CCLOGINFO("deallocing AutoreleasePool: %p", this);    clear();    PoolManager::getInstance()->pop();}

哦,原来,我们把AutoreleasePool对象又放到了PoolManager里了;原 来,PoolManager类就是用来管理所有的AutoreleasePool的类,也是使用的单例模式来实现的。该PoolManger有一个存放 AutoreleasePool对象指针的stack,该stack是由std::vector实现的。需要注意的是,cocos2d-x的单例类都不是 线程安全的,跟内存管理紧密相关的PoolManager类也不例外,因此在多线程中使用cocos2d-x的接口需要特别注意内存管理的问题。关于更安 全的单例模式,感兴趣的同学可以去阅读这篇《C++设计模式——单例模式》。接下来,我们先看看PoolManager的头文件定义。

class CC_DLL PoolManager{public:    /**    * 获得单例    */    static PoolManager* getInstance();    /**    * 销毁单例    */    static void destroyInstance();    /**    * 获得当前的autorelease pool,在引擎中,至少会有一个autorelease pool    * 在需要的时候,我们可以创建我们自己的release pool,然后将这个autorelease pool添加到PoolManager中    */    AutoreleasePool *getCurrentPool() const;    /**    * 判断指定的对象是否在其中的一个autorelease pool中    */    bool isObjectInPools(Ref* obj) const;    friend class AutoreleasePool;private:    PoolManager();    ~PoolManager();    /**    * 将AutoreleasePool对象添加到PoolManager中    */    void push(AutoreleasePool *pool);    /**    * 从PoolManager中移除AutoreleasePool对象    */    void pop();    static PoolManager* s_singleInstance;    /**    * 用来保存所有的AutoreleasePool对象    */    std::deque<AutoreleasePool*> _releasePoolStack;    AutoreleasePool *_curReleasePool;};

关于PoolManager中各个函数的实现也是非常简单的,这里不做累述,各位可以去阅读Cocos2d-x的源码。

问题来了

说了这么多,代码也列了这么多,我们create一个对象以后,放到了 AutoreleasePool中去了,最终,在调用AutoreleasePool的clear函数的时候,会对AutoreleasePool管理的 所有对象依次调用release操作。啊哈!貌似哪里不对,我一直都没有说最终谁会调用这个clear函数啊?是的。看下面这段在导演类中的代码,我想你 会明白的。

void DisplayLinkDirector::mainLoop(){    if (_purgeDirectorInNextLoop)    {        _purgeDirectorInNextLoop = false;        purgeDirector();    }    else if (! _invalid)    {        drawScene();        // release the objects        PoolManager::getInstance()->getCurrentPool()->clear();    }}

上面的代码说明的事实是:在图像渲染的主循环中,如果当前的图形对象是在当前帧,则调用显示函数,并 调用AutoreleasePool::clear()减少这些对象的引用计数。mainLoop是每一帧都会自动调用的,所以下一帧时这些对象都被当前 的AutoreleasePool对象release了一次。这也是AutoreleasePool「自动」的来由。

总结

好了,总结的差不多了,对于Cocos2d-x中的内存管理总结的差不多了。对于Cocos2d-x 中的内存管理,我个人认为,请时刻关注着这个对象的引用计数,retain和release,new和autorelease需要匹配使用,防止不必要的 错误发生。总结了这么多,还是那句话。

纸上得来终觉浅,绝知此事要躬行。

只有经过实际的使用,在经过代码的洗练,才能更好的去掌握这些。在Cocos2d-x中,很多地方已 经进行了autorelease,或者retain了,我们就不必再次进行这些操作,比如create,再比如在调用addChild方法添加子节点时, 自动调用了retain。对应的通过removeChild,移除子节点时,自动调用了release。这些地方稍微不注意,就可能会让你掉入“坑”中。 努力吧,伙计们。

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 松本菜奈实 华而不实 松本莱奈实在线看 实线变道怎么处罚 实分析与复分析 耐实电脑椅 振实台 会计实帐书 击实试验 程实 眼见为实耳听为虚 眼见未必为实 陶哲轩实分析 重型击实仪 津实 威实oa 土工击实试验 振实密度 家实电饭煲 橘小实蝇 耐实电脑椅怎么样 肉实诚怎么减肥 耐实拓刹车片 大手印实修心髓 实肉怎么减肥 土的击实试验 震实机 三维振实台 奥龙堡省实羽毛球馆 欧耐实无气喷涂机 蜷川实花 恶女花魁 土壤电动击实仪 会计实账演练一本就够 实优特电子有限公司 实干初中怕到哭手机观看 60元看70场实景演出在哪里 江泉实业 新潮实业 荣华实业 实业帝国