Cocos2d-x 内存管理剖析(1)

下面这篇文章将深入cocos2dx引擎的源代码,剖析cocos2dx中内存的管理机制的实现。使用cocos2dx-2.1.5版本。

参考文章:点击打开链接 

一、前言:

我们都知道在c++中,创建实例化的对象都是使用new关键字,在不使用或者不需要的时候必须手动使用delete关键字去掉这个对象,否则new出来的对象就不能回收,造成内存泄露。


例如(我们创建了一个大小为10的数组)

[cpp]  view plain copy
  1. int* p = new int[10];  
  2.     for (int i = 0; i<10; i++) {  
  3.         p[i] = i;  
  4.     }  

如果使用结束后,不手动  delete [] p; 那么就会造成这个数组的内存泄露。


二、Cocos2d-x采用引用计数方式对对象进行内存管理

关于引用计数方式的内存管理,熟悉ios开发的人来说应该不陌生;其内存管理的过程大致是:为每一个对象保存一个引用计数,当引用计数大于0的时候,对象继续保存在内存中;但是当引用计数等于0的时候,自动释放这个对象,这个过程就是autorelease(自动管理)。


在Cocos2d-x中,CCObject是整个引擎的基础类,其他的类都是继承自这个类而来。


那么,首先从这个类开始进行内存管理的剖析:

在 libs/cocos2dx/cocoa/ 位置下找到这个类文件。

[cpp]  view plain copy
  1. class CC_DLL CCObject : public CCCopying  
  2. {  
  3. public:  
  4.     // object id, CCScriptSupport need public m_uID  
  5.     unsigned int        m_uID;  
  6.     // Lua reference id  
  7.     int                 m_nLuaID;  
  8. protected:  
  9.     // count of references  
  10.     unsigned int        m_uReference;  
  11.     // count of autorelease  
  12.     unsigned int        m_uAutoReleaseCount;  
  13. public:  
  14.     CCObject(void);  
  15.     virtual ~CCObject(void);  
  16.       
  17.     void release(void);  
  18.     void retain(void);  
  19.     CCObject* autorelease(void);  
  20.     CCObject* copy(void);  
  21.     bool isSingleReference(voidconst;  
  22.     unsigned int retainCount(voidconst;  
  23.     virtual bool isEqual(const CCObject* pObject);  
  24.   
  25.     virtual void acceptVisitor(CCDataVisitor &visitor);  
  26.   
  27.     virtual void update(float dt) {CC_UNUSED_PARAM(dt);};  
  28.       
  29.     friend class CCAutoreleasePool;  
  30. };  
说明:

public 中的两个属性:对象的ID编号(其中第二个是用于Lua中的);


protected 中的两个属性:

  m_uReference表示对象的引用计数;

  m_uAutoReleaseCount 则表示对象是否自动管理,也就是表示是否autorelease。注意到 m_uAutoReleaseCount 这个变量是unsigned int类型,在之前一些版本中,曾经是使用 bool m_bManaged 这个bool变量,其实本质是一样的,如果对象autorelease(自动管理),那么 ++m_uAutoReleaseCount,只要这个值非零,那么这个对象就是autorelease的。而假如是bool变量的话,那么就是让其变成true就是autorelease了。


前面说到,对对象的内存管理是使用到引用计数,那么我们就看看CCObject中,引用计数 m_uReference 是如何变化的吧!

首先我们进入CCObject的构造方法中:

[cpp]  view plain copy
  1. CCObject::CCObject(void)  
  2. : m_nLuaID(0)  
  3. , m_uReference(1) // when the object is created, the reference count of it is 1  
  4. , m_uAutoReleaseCount(0)  
  5. {  
  6.     static unsigned int uObjectCount = 0;  
  7.   
  8.     m_uID = ++uObjectCount;  
  9. }  

显然,创建一个CCObject对象之后,其引用计数 m_uReference 变为1,而 m_uAutoReleaseCount 为0,表示对象还没有autorelease。


接着就是retain和release方法了,前者对引用计数加1,后者引用计数减1。

[cpp]  view plain copy
  1. void CCObject::release(void)  
  2. {  
  3.     CCAssert(m_uReference > 0, "reference count should greater than 0");  
  4.     --m_uReference;  
  5.   
  6.     if (m_uReference == 0)  
  7.     {  
  8.         delete this;  
  9.     }  
  10. }  
  11.   
  12. void CCObject::retain(void)  
  13. {  
  14.     CCAssert(m_uReference > 0, "reference count should greater than 0");  
  15.   
  16.     ++m_uReference;  
  17. }  

其中release方法中,如果引用计数 m_uReference 为0,那么就自动删除这个对象进行回收。

好了,通过上面的介绍我们就可以知道cocos2dx中的引用计数是怎么一回事了,接下来就是 :引擎中是如何对实例对象进行自动管理。



三、实例对象自动管理autorelease

下面从一个CCNode创建的生命历程来讲一下这个自动管理的过程。

在 libs/cocos2dx/base_nodes 中找到CCNode的类文件。

进入其中的create方法。

[cpp]  view plain copy
  1. CCNode * CCNode::create(void)  
  2. {  
  3.     CCNode * pRet = new CCNode();  
  4.     if (pRet && pRet->init())  
  5.     {  
  6.         pRet->autorelease();  
  7.     }  
  8.     else  
  9.     {  
  10.         CC_SAFE_DELETE(pRet);  
  11.     }  
  12.     return pRet;  
  13. }  

其中 create 创建的过程是先用 new创建对象,然后将对象自动管理 autorelease。(所以我们就知道了,我们一般创建节点对象都使用create,其过程就是一个new+autorelease = create


进入其中的autorelease方法

[cpp]  view plain copy
  1. CCObject* CCObject::autorelease(void)  
  2. {  
  3.     CCPoolManager::sharedPoolManager()->addObject(this);  
  4.     return this;  
  5. }  
进入其中的addObject方法

[cpp]  view plain copy
  1. void CCPoolManager::addObject(CCObject* pObject)  
  2. {  
  3.     getCurReleasePool()->addObject(pObject);  
  4. }  
再进入其中的addObject方法

[cpp]  view plain copy
  1. void CCAutoreleasePool::addObject(CCObject* pObject)  
  2. {  
  3.     m_pManagedObjectArray->addObject(pObject);  
  4.   
  5.     CCAssert(pObject->m_uReference > 1, "reference count should be greater than 1");  
  6.     ++(pObject->m_uAutoReleaseCount);  
  7.     pObject->release(); // no ref count, in this case autorelease pool added.  
  8. }  

首先看一下在这个过程中我们遇到了这两个类:CCPoolManager 和 CCAutoreleasePool,前者是对象自动管理类,后者是对象自动管理(释放)池。后面再详细分析一下这个autorelease过程。


先看到CCAutoreleasePool这个类:

[cpp]  view plain copy
  1. class CC_DLL CCAutoreleasePool : public CCObject  
  2. {  
  3.     CCArray*    m_pManagedObjectArray;      
  4. public:  
  5.     CCAutoreleasePool(void);  
  6.     ~CCAutoreleasePool(void);  
  7.   
  8.     void addObject(CCObject *pObject);  
  9.     void removeObject(CCObject *pObject);  
  10.   
  11.     void clear();  
  12. };  
类中有一个  m_pManagedObjectArray  数组,我们将对象自动管理后,都是将对象添加到这个数组中的,所以说是对象自动管理(释放)池。值得注意的是,这个类也是继承自CCObject,那么显然其内存管理形式也采用引用计数的。


其中的addObject方法就是将需要自动管理的对象添加到这个m_pManagedObjectArray数组中,那么就说将对象进行了自动管理autorelease。


下面进入这个方法看看:

[cpp]  view plain copy
  1. void CCAutoreleasePool::addObject(CCObject* pObject)  
  2. {  
  3.     m_pManagedObjectArray->addObject(pObject);  
  4.   
  5.     CCAssert(pObject->m_uReference > 1, "reference count should be greater than 1");  
  6.     ++(pObject->m_uAutoReleaseCount);  
  7.     pObject->release(); // no ref count, in this case autorelease pool added.  
  8. }  

首先是将对象添加到数组中,然后将对象自动管理变量++,那么对象的 m_uAutoReleaseCount =1,也就是表明其为自动管理对象。


然后对对象进行release,也即对对象的引用计数进行减1的操作,这个是为什么呢?


下面分析一个这个原因:

①首先一个CCNode对象被创建,使用的是new,也就是调用了构造方法,那么其引用计数 m_uReference=1。这个应该不难理解吧:CCNode是继承自CCObject,而在前面CCObject的构造方法中就知道,new创建一个实例对象后,其引用计数m_uReference=1,而是否自动管理变量 m_uAutoReleaseCount=0(表示对象未添加到自动管理(释放)池中自动管理)。


②接着对象autorelease,那么就是将对象添加到自动释放池中,而其中的:

m_pManagedObjectArray->addObject(pObject); 将被管理对象添加到自动管理池中的过程中(将对象添加到数组中),其会对对象进行retain的,所以 m_uReference 就变成了 m_uReference=2。


下面跟踪一下这个过程:

[cpp]  view plain copy
  1. void CCArray::addObject(CCObject* object)  
  2. {  
  3.     ccArrayAppendObjectWithResize(data, object);  
  4. }  

[cpp]  view plain copy
  1. /** Appends an object. Capacity of arr is increased if needed. */  
  2. void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object)  
  3. {  
  4.     ccArrayEnsureExtraCapacity(arr, 1);  
  5.     ccArrayAppendObject(arr, object);  
  6. }  

[cpp]  view plain copy
  1. /** Appends an object. Behavior undefined if array doesn't have enough capacity. */  
  2. void ccArrayAppendObject(ccArray *arr, CCObject* object)  
  3. {  
  4.     CCAssert(object != NULL, "Invalid parameter!");  
  5.     object->retain();  
  6.     arr->arr[arr->num] = object;  
  7.     arr->num++;  
  8. }  

看到最后方法中 object->retain(); 了吧!


③所以最后的release是为了将引用计数重新变成为1。这样的话,从对象创建,到被放到对象自动管理池,引用计数依然为1,被管理值也为1(表征对象是自动管理)。



下面看看 CCPoolManager 类,这个类顾名思义就知道是对对象自动释放池进行管理的类,它是一个单例类。

[cpp]  view plain copy
  1. class CC_DLL CCPoolManager  
  2. {  
  3.     CCArray*    m_pReleasePoolStack;      
  4.     CCAutoreleasePool*                    m_pCurReleasePool;  
  5.   
  6.     CCAutoreleasePool* getCurReleasePool();  
  7. public:  
  8.     CCPoolManager();  
  9.     ~CCPoolManager();  
  10.     void finalize();  
  11.     void push();  
  12.     void pop();  
  13.   
  14.     void removeObject(CCObject* pObject);  
  15.     void addObject(CCObject* pObject);  
  16.   
  17.     static CCPoolManager* sharedPoolManager();  
  18.     static void purgePoolManager();  
  19.   
  20.     friend class CCAutoreleasePool;  
  21. };  
这个类中的属性  m_pCurReleasePool 表示当前的自动释放池,可以使用  getCurReleasePool()这个方法获取到当前的这个属性,进入这个方法:


[cpp]  view plain copy
  1. CCAutoreleasePool* CCPoolManager::getCurReleasePool()  
  2. {  
  3.     if(!m_pCurReleasePool)  
  4.     {  
  5.         push();  
  6.     }  
  7.   
  8.     CCAssert(m_pCurReleasePool, "current auto release pool should not be null");  
  9.   
  10.     return m_pCurReleasePool;  
  11. }  

这个类中还有一个属性m_pReleasePoolStack 属于CCArray类型,表示对象自动释放池数组,其中可以有多个对象自动释放池。


比如说,在上面的 getCurReleasePool() 方法中,如果 m_pCurReleasePool 为空,那么就要进入push()方法创建一个对象自动释放池,并且添加到m_pReleasePoolStack中。

[cpp]  view plain copy
  1. void CCPoolManager::push()  
  2. {  
  3.     CCAutoreleasePool* pPool = new CCAutoreleasePool();       //ref = 1  
  4.     m_pCurReleasePool = pPool;  
  5.   
  6.     m_pReleasePoolStack->addObject(pPool);                   //ref = 2  
  7.   
  8.     pPool->release();                                       //ref = 1  
  9. }  

如代码所示,push()操作 new 了一个自动释放池对象,并且将它赋值给 当前自动释放池(m_pCurReleasePool),然后将这个new的自动释放池对象放到CCPoolManager 里面的自动释放池数组中。


注意过程中对其引用计数的控制,自动释放池本身也是继承ccobject的,也有自己的引用计数,受相同的内存管理方式。其中的注释就表明了自动释放池对象的引用计数。


好,大致已经介绍了CCPoolManager 和 CCAutoreleasePool 这两个类,那么再来看将对象自动管理autorelease的过程就比较简单了。


首先调用构造方法,new一个对象(我们称之为object),然后这个对象调用autorelease方法,接着就是得到

CCPoolManager的单例变量,用getCurReleasePool() 方法获取到当前的对象自动释放池对象(如果没有的话,就新建一个对象自动释放池对象,并且添加到 对象自动释放池数组中),然后把这个object对象添加到自动释放池中的

m_pManagedObjectArray 数组中,执行完这个过程,这个新建的object对象的引用计数值为1,被管理状态值也为1(表征该对象是自动管理的)


下面通过一个简单的例子来说明上面所讲述的内容:

1、new创建一个对象,还没有autorelease。


Cocos2d-x 内存管理剖析(1)_第1张图片

2、对对象进行autorelease操作



Cocos2d-x 内存管理剖析(1)_第2张图片


3、对对象进行retain操作:

Cocos2d-x 内存管理剖析(1)_第3张图片


Cocos2d-x 内存管理剖析(1)_第4张图片

你可能感兴趣的:(内存管理,cocos2d-x)