下面这篇文章将深入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; }
二、Cocos2d-x采用引用计数方式对对象进行内存管理
关于引用计数方式的内存管理,熟悉ios开发的人来说应该不陌生;其内存管理的过程大致是:为每一个对象保存一个引用计数,当引用计数大于0的时候,对象继续保存在内存中;但是当引用计数等于0的时候,自动释放这个对象,这个过程就是autorelease(自动管理)。
在Cocos2d-x中,CCObject是整个引擎的基础类,其他的类都是继承自这个类而来。
那么,首先从这个类开始进行内存管理的剖析:
在 libs/cocos2dx/cocoa/ 位置下找到这个类文件。
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); 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) // when the object is created, the reference count of it is 1 , m_uAutoReleaseCount(0) { static unsigned int uObjectCount = 0; m_uID = ++uObjectCount; }
接着就是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; }
好了,通过上面的介绍我们就可以知道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; }
进入其中的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(); // no ref count, in this case autorelease pool added. }
首先看一下在这个过程中我们遇到了这两个类: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(); // no ref count, in this case autorelease pool added. }
然后对对象进行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); }
/** Appends an object. Capacity of arr is increased if needed. */ void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object) { ccArrayEnsureExtraCapacity(arr, 1); ccArrayAppendObject(arr, object); }
/** Appends an object. Behavior undefined if array doesn't have enough capacity. */ void ccArrayAppendObject(ccArray *arr, CCObject* object) { CCAssert(object != NULL, "Invalid parameter!"); object->retain(); arr->arr[arr->num] = object; arr->num++; }
③所以最后的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(); //ref = 1 m_pCurReleasePool = pPool; m_pReleasePoolStack->addObject(pPool); //ref = 2 pPool->release(); //ref = 1 }
注意过程中对其引用计数的控制,自动释放池本身也是继承ccobject的,也有自己的引用计数,受相同的内存管理方式。其中的注释就表明了自动释放池对象的引用计数。
好,大致已经介绍了CCPoolManager 和 CCAutoreleasePool 这两个类,那么再来看将对象自动管理autorelease的过程就比较简单了。
首先调用构造方法,new一个对象(我们称之为object),然后这个对象调用autorelease方法,接着就是得到
CCPoolManager的单例变量,用getCurReleasePool() 方法获取到当前的对象自动释放池对象(如果没有的话,就新建一个对象自动释放池对象,并且添加到 对象自动释放池数组中),然后把这个object对象添加到自动释放池中的
m_pManagedObjectArray 数组中,执行完这个过程,这个新建的object对象的引用计数值为1,被管理状态值也为1(表征该对象是自动管理的)。
下面通过一个简单的例子来说明上面所讲述的内容:
1、new创建一个对象,还没有autorelease。
2、对对象进行autorelease操作
3、对对象进行retain操作: