cocos2dx 内存管理

内存管理中经常遇到的问题:内存泄露,内存溢出。


在cocos2dx中用的是引用计数和自动释放池的技术,由于熟悉objective-c语言,所以对这两个概念不会很陌生。

一、引用计数
     引用计数是自动内存管理的基础:在对象里增加一个引用计数,当外部引用增加时,计数器加1,当外部引用消失时,计数器减1 。
看一下CCObject源码:
class CC_DLL CCObject : public CCCopying
{
public:
    // object id, CCScriptSupport need public m_uID
    unsigned int        m_uID;
    // Lua reference id
    int                 m_nLuaID ;
protected:
    // count of references
    unsigned int        m_uReference;
    // count of autorelease
    unsigned int        m_uAutoReleaseCount;
public:
    CCObject (void);
    /**
     *  @lua NA
     */
    virtual ~CCObject( void);
   
    void release (void);
    void retain (void);
    CCObject * autorelease( void);
    CCObject * copy( void);
    bool isSingleReference (void) const;
    unsigned int retainCount( void) const;
    virtual bool isEqual( const CCObject* pObject);

    virtual void acceptVisitor( CCDataVisitor &visitor );

    virtual void update( float dt) {CC_UNUSED_PARAM (dt);};
   
    friend class CCAutoreleasePool;
};

     其中m_uReference就是用于计数的整形变量,在这里就记录了当前对象的引用计数。
     跟objective-c一样,retain是计数器加1, release是计数器减1, 计数器为0的时候会自动清除对象。
     加1 减1 操作如下:
void CCObject:: release(void )
{
    CCAssert (m_uReference > 0, "reference count should greater than 0");
    --m_uReference ;

    if (m_uReference == 0)
    {
        delete this ;
    }
}

void CCObject:: retain(void )
{
    CCAssert (m_uReference > 0, "reference count should greater than 0");

    ++m_uReference ;
}

     上面的引用计数只是自动管理的方式,通过这种方式cocos2dx引擎会晓得何时释放对象。
     在这里之前需要保证引用计数的准确性,建议:
     1、开始对应着结束,创建(new,copy)了对象就一定要释放(release)。
     2、引用了就要释放,  也就是说 调用了retain后就要调用release。
     3、参数传递需要更替引用。   当对象指针作为参数传递的时候,在函数内需要引用对象,这时候就需要释放掉旧的对象,然后增加新的对象:
     void a(B *b) {
          retain(b);
          release(m_b);
          m_b = b;
     }
          来解释一下上面函数为何先增加 再减少引用计数,然后赋值:
          1、retain(b)是因为 b指向一个B 对象,retain一次是为了保证b不被销毁。保留一次值。
          2、release(m_b) 是因为m_b是一个属性,在这里实际上是要释放掉上次赋给他的值。
          3、这里是m_b和b指向相同的对象,拥有相同的引用计数。    调用完毕后,他们的引用计数是1。


二、自动释放池
          CCPoolManager  这个类是自动释放吃计数的精髓。

     我们可以将对象放在自动释放吃中,在引擎每次绘制周期结束的时候,就会自动释放池中的对象。
     具体的使用:
     CCObject *obj = new CCObject;
     obj->autorelease();  将对象加入自动释放池中。

     1、原理
          如上面代码,创建一个obj对象,通过autorelease将他加入到自动释放池中, 使用函数设置自动释放功能时,内存管理类CCPoolManager就会把这个当前对象加入到管理池中,等到特定的时候,内存管理着就会遍历其所管理的每一个对象,逐个调用对象的释放函数进行释放。
      CCObject* CCObject ::autorelease( void)
{
    CCPoolManager ::sharedPoolManager()-> addObject(this );
    return this;
}
     再看addObject函数:
void CCPoolManager:: addObject(CCObject * pObject)
{
    getCurReleasePool ()->addObject( pObject);
}
CCAutoreleasePool* CCPoolManager ::getCurReleasePool()
{
    if(! m_pCurReleasePool)
    {
        push ();
    }

    CCAssert (m_pCurReleasePool, "current auto release pool should not be null");

    return m_pCurReleasePool ;
}
  
void CCAutoreleasePool:: addObject(CCObject * pObject)
{
    m_pManagedObjectArray ->addObject( pObject);

    CCAssert (pObject-> m_uReference > 1, "reference count should be greater than 1" );
    ++(pObject ->m_uAutoReleaseCount);
    pObject ->release(); // no ref count, in this case autorelease pool added.
}
       可以看到, CCArray *     m_pManagedObjectArray  ;     这是一个数组;
        在上面函数的最后一句调用了release, 这是因为  在前面addObject添加到数组的时候,增加了一次对象的引用计数。
     
     下面看看对象自动释放的过程:
void  CCPoolManager ::  push ()
{
    CCAutoreleasePool  *  pPool  =  new  CCAutoreleasePool  ();         //ref = 1
    m_pCurReleasePool  =  pPool ;

    m_pReleasePoolStack  -> addObject (  pPool );                     //ref = 2  加入释放池容器

    pPool  -> release ();                                          //ref = 1
}

void  CCPoolManager ::  pop ()
{
     if  (!  m_pCurReleasePool )
     {
         return ;
     }
     
      int  nCount  =  m_pReleasePoolStack -> count  ();    //获取当前释放池中对象的数目

    m_pCurReleasePool  -> clear ();  //释放自动释放池中的内容
 
       if ( nCount  >  1  )  //自动释放吃多于一个   下面就减少一个
       {
        m_pReleasePoolStack  -> removeObjectAtIndex (  nCount - 1  );

//         if(nCount > 1)
//         {
//             m_pCurReleasePool = m_pReleasePoolStack->objectAtIndex(nCount - 2);
//             return;
//         }
        m_pCurReleasePool  =  ( CCAutoreleasePool *)  m_pReleasePoolStack -> objectAtIndex  ( nCount  -  2 );
     }

     /*m_pCurReleasePool = NULL;*/
}

          代码中 ,  m_pReleasePoolStack    是保存多个释放吃的地方,    要注意第一个自动释放池实在CCDirector构造函数中创建的。

在CCPoolManager类中,你会看到他可以保存多个自动释放池, 自动释放池中保存的是对象。  所以自动释放吃的创建和释放是由Manager来负责的。

来看一下是怎么释放的:
void CCAutoreleasePool:: clear()
{
    if( m_pManagedObjectArray->count () > 0)
    {
        //CCAutoreleasePool* pReleasePool;
#ifdef _DEBUG
        int nIndex = m_pManagedObjectArray->count () - 1;
#endif

        CCObject * pObj = NULL;
        CCARRAY_FOREACH_REVERSE (m_pManagedObjectArray, pObj)
        {
            if(!pObj )  //看对象是否存在
                break;

            --(pObj-> m_uAutoReleaseCount);
            //(*it)->release();
            //delete (*it);
#ifdef _DEBUG
            nIndex --;
#endif
        }

        m_pManagedObjectArray ->removeAllObjects(); //释放池中的所有对象
    }
}

          在这里,在释放之前,pObject的标记autorelease count会减1 。  这样是为了避免释放对象后产生错误。  所以,自动释放池技术也不是永远都可以依赖的,他也是依赖引用计数,只要引用存在,内存就不能回收。
     cocos2dx 引擎通过管理模式来实现自动释放池的技术。   在这里很多地方都使用了管理模式。   管理模式+缓冲区  可以很好地管理内存资源。

三、管理模式
         在cocos2dx中,不仅仅只有前面使用的CCPoolManager,  还有动作管理者,脚本管理者等等。
          1、在引擎当中,只会存在一个管理者,如:自动释放池管理者,动作管理者等等。  为了保证管理者的唯一性,管理者模式的对象都会是单例。  通过share前缀函数获得单例,purge前缀函数释放单例。
          2、管理模式可以管理对象,他会负责所管理对象的创建和销毁。
          3、管理模式的优点是为一组相关的对象提供一个统一的全局访问点,同时可以提供一些简洁的接口来获取和操作这些对象。   用他来缓存游戏中的常有资源,可以提高游戏运行时的性能。

     看看缓冲区:
          游戏中有的资源不会被常用,只出现几次,但是有的资源经常会出现,这样我们就可以将他们放在缓冲区了。
          这样存在内存不够的情况,这样在内存占用过多的时候可以来释放相应的缓冲区。

          在缓冲区的内部也使用了引用计数的方式来管理对象资源,可以通过retain和release来进行控制。
          我们也可以通过缓冲区进行预加载的操作。  但是要注意清理。






你可能感兴趣的:(cocos2d-x)