下面这篇文章将深入cocos2dx引擎的源代码,剖析cocos2dx中内存的管理机制的实现。使用cocos2dx-2.1.5版本。
参考文章:点击打开链接
一、前言:
我们都知道在c++中,创建实例化的对象都是使用new关键字,在不使用或者不需要的时候必须手动使用delete关键字去掉这个对象,否则new出来的对象就不能回收,造成内存泄露。
例如(我们创建了一个大小为10的数组)
- int* p = new int[10];
- for (int i = 0; i<10; i++) {
- p[i] = i;
- }
如果使用结束后,不手动
delete [] p; 那么就会造成这个数组的内存泄露。
二、Cocos2d-x采用引用计数方式对对象进行内存管理
关于引用计数方式的内存管理,熟悉ios开发的人来说应该不陌生;其内存管理的过程大致是:为每一个对象保存一个引用计数,当引用计数大于0的时候,对象继续保存在内存中;但是当引用计数等于0的时候,自动释放这个对象,这个过程就是autorelease(自动管理)。
在Cocos2d-x中,CCObject是整个引擎的基础类,其他的类都是继承自这个类而来。
那么,首先从这个类开始进行内存管理的剖析:
在 libs/cocos2dx/cocoa/ 位置下找到这个类文件。
- class CC_DLL CCObject : public CCCopying
- {
- public:
-
- unsigned int m_uID;
-
- int m_nLuaID;
- protected:
-
- unsigned int m_uReference;
-
- unsigned int m_uAutoReleaseCount;
- public:
- CCObject(void);
- 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;
- };
说明:
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的构造方法中:
- CCObject::CCObject(void)
- : m_nLuaID(0)
- , m_uReference(1)
- , m_uAutoReleaseCount(0)
- {
- static unsigned int uObjectCount = 0;
-
- m_uID = ++uObjectCount;
- }
显然,创建一个CCObject对象之后,其引用计数 m_uReference 变为1,而 m_uAutoReleaseCount 为0,表示对象还没有autorelease。
接着就是retain和release方法了,前者对引用计数加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;
- }
其中release方法中,如果引用计数 m_uReference 为0,那么就自动删除这个对象进行回收。
好了,通过上面的介绍我们就可以知道cocos2dx中的引用计数是怎么一回事了,接下来就是 :引擎中是如何对实例对象进行自动管理。
三、实例对象自动管理autorelease
下面从一个CCNode创建的生命历程来讲一下这个自动管理的过程。
在 libs/cocos2dx/base_nodes 中找到CCNode的类文件。
进入其中的create方法。
- CCNode * CCNode::create(void)
- {
- CCNode * pRet = new CCNode();
- if (pRet && pRet->init())
- {
- pRet->autorelease();
- }
- else
- {
- CC_SAFE_DELETE(pRet);
- }
- return pRet;
- }
其中 create 创建的过程是先用 new创建对象,然后将对象自动管理 autorelease。(所以我们就知道了,我们一般创建节点对象都使用create,其过程就是一个new+autorelease = create)
进入其中的autorelease方法
- CCObject* CCObject::autorelease(void)
- {
- CCPoolManager::sharedPoolManager()->addObject(this);
- return this;
- }
进入其中的addObject方法
- void CCPoolManager::addObject(CCObject* pObject)
- {
- getCurReleasePool()->addObject(pObject);
- }
再进入其中的addObject方法
- 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();
- }
首先看一下在这个过程中我们遇到了这两个类:CCPoolManager 和 CCAutoreleasePool,前者是对象自动管理类,后者是对象自动管理(释放)池。后面再详细分析一下这个autorelease过程。
先看到CCAutoreleasePool这个类:
- class CC_DLL CCAutoreleasePool : public CCObject
- {
- CCArray* m_pManagedObjectArray;
- public:
- CCAutoreleasePool(void);
- ~CCAutoreleasePool(void);
-
- void addObject(CCObject *pObject);
- void removeObject(CCObject *pObject);
-
- void clear();
- };
类中有一个
m_pManagedObjectArray
数组,我们将对象自动管理后,都是将对象添加到这个数组中的,所以说是对象自动管理(释放)池。值得注意的是,这个类也是继承自CCObject,那么显然其内存管理形式也采用引用计数的。
其中的addObject方法就是将需要自动管理的对象添加到这个m_pManagedObjectArray数组中,那么就说将对象进行了自动管理autorelease。
下面进入这个方法看看:
- 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();
- }
首先是将对象添加到数组中,然后将对象自动管理变量++,那么对象的 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。
下面跟踪一下这个过程:
- void CCArray::addObject(CCObject* object)
- {
- ccArrayAppendObjectWithResize(data, object);
- }
-
- void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object)
- {
- ccArrayEnsureExtraCapacity(arr, 1);
- ccArrayAppendObject(arr, object);
- }
-
- void ccArrayAppendObject(ccArray *arr, CCObject* object)
- {
- CCAssert(object != NULL, "Invalid parameter!");
- object->retain();
- arr->arr[arr->num] = object;
- arr->num++;
- }
看到最后方法中 object->retain(); 了吧!
③所以最后的release是为了将引用计数重新变成为1。这样的话,从对象创建,到被放到对象自动管理池,引用计数依然为1,被管理值也为1(表征对象是自动管理)。
下面看看 CCPoolManager 类,这个类顾名思义就知道是对对象自动释放池进行管理的类,它是一个单例类。
- class CC_DLL CCPoolManager
- {
- CCArray* m_pReleasePoolStack;
- CCAutoreleasePool* m_pCurReleasePool;
-
- CCAutoreleasePool* getCurReleasePool();
- public:
- CCPoolManager();
- ~CCPoolManager();
- void finalize();
- void push();
- void pop();
-
- void removeObject(CCObject* pObject);
- void addObject(CCObject* pObject);
-
- static CCPoolManager* sharedPoolManager();
- static void purgePoolManager();
-
- friend class CCAutoreleasePool;
- };
这个类中的属性
m_pCurReleasePool 表示当前的自动释放池,可以使用
getCurReleasePool()这个方法获取到当前的这个属性,进入这个方法:
- CCAutoreleasePool* CCPoolManager::getCurReleasePool()
- {
- if(!m_pCurReleasePool)
- {
- push();
- }
-
- CCAssert(m_pCurReleasePool, "current auto release pool should not be null");
-
- return m_pCurReleasePool;
- }
这个类中还有一个属性m_pReleasePoolStack 属于CCArray类型,表示对象自动释放池数组,其中可以有多个对象自动释放池。
比如说,在上面的 getCurReleasePool() 方法中,如果 m_pCurReleasePool 为空,那么就要进入push()方法创建一个对象自动释放池,并且添加到m_pReleasePoolStack中。
- void CCPoolManager::push()
- {
- CCAutoreleasePool* pPool = new CCAutoreleasePool();
- m_pCurReleasePool = pPool;
-
- m_pReleasePoolStack->addObject(pPool);
-
- pPool->release();
- }
如代码所示,push()操作 new 了一个自动释放池对象,并且将它赋值给 当前自动释放池(m_pCurReleasePool),然后将这个new的自动释放池对象放到CCPoolManager 里面的自动释放池数组中。
注意过程中对其引用计数的控制,自动释放池本身也是继承ccobject的,也有自己的引用计数,受相同的内存管理方式。其中的注释就表明了自动释放池对象的引用计数。
好,大致已经介绍了CCPoolManager 和 CCAutoreleasePool 这两个类,那么再来看将对象自动管理autorelease的过程就比较简单了。
首先调用构造方法,new一个对象(我们称之为object),然后这个对象调用autorelease方法,接着就是得到
CCPoolManager的单例变量,用getCurReleasePool() 方法获取到当前的对象自动释放池对象(如果没有的话,就新建一个对象自动释放池对象,并且添加到 对象自动释放池数组中),然后把这个object对象添加到自动释放池中的
m_pManagedObjectArray 数组中,执行完这个过程,这个新建的object对象的引用计数值为1,被管理状态值也为1(表征该对象是自动管理的)。
下面通过一个简单的例子来说明上面所讲述的内容:
1、new创建一个对象,还没有autorelease。
2、对对象进行autorelease操作
3、对对象进行retain操作: