一、常用内存管理技术
1、智能指针
cocos的内存管理没有用智能指针,智能指针,c++有相应的类型,代码如下:
#include <memory>
using namespace std;
class Obj{
public:
~Obj(){ printf("destruct is called\n"); }
};
void testAutoptr(){
auto_ptr<Obj> obj1(new Obj);
}
上面最后obj1的析构函数被调用了。new Obj在堆中申请内存,释放需要delete,但是上面代码并没有,查看
auto_ptr
<
Obj
>的析构函数如下:
_LIBCPP_INLINE_VISIBILITY ~auto_ptr() throw() {delete __ptr_;}
delete __ptr_把__ptr_指向的对象obj给delete了,obj的析构函数接着被调用。auto_ptr<Obj>的构造函数如下:
_LIBCPP_INLINE_VISIBILITY explicit auto_ptr(_Tp* __p = 0) throw() : __ptr_(__p) {}
_LIBCPP_INLINE_VISIBILITY auto_ptr(auto_ptr& __p) throw() : __ptr_(__p.release()) {}
template<class _Up> _LIBCPP_INLINE_VISIBILITY auto_ptr(auto_ptr<_Up>& __p) throw()
: __ptr_(__p.release()) {}
上面第一个构造参数是原型指针,第二个是
auto_ptr
<
Obj
>引用,第三个是
auto_ptr
<_Up引用>,原型不需要一样可以把auto_ptr<SubCls>的给auto_ptr<Cls>,只要类型转换正确就行,代码如下:
#include <memory>
using namespace std;
class Cls{
public:
virtual ~Cls(){ printf("Cls Destructor is called\n"); }
};
class SubCls: public Cls{
public:
virtual ~SubCls(){ printf("SubCls Destructor is called\n"); }
};
void testAutoptr(){
auto_ptr<SubCls> obj1(new SubCls);
auto_ptr<Cls> obj2(obj1);
}
输出:
SubCls Destructor is called
Cls Destructor is called
Program ended with exit code: 0
上面代码把子类SubCls的智能智能给了父类
auto_ptr
<Cls>的指针指针,这个向上转型是对的,向下就不行了,向上转型,父类的方法在子类中都是定义的,所以OK,向下的话父类许多函数在子类中并没有定义,这是不行的。还有注意要用虚析构函数。template<class _Up> _LIBCPP_INLINE_VISIBILITY auto_ptr(auto_ptr<_Up>& __p)throw()
:
__ptr_
(__p.release()) {}这段代码里调用了__p.release(),查看如下:
_LIBCPP_INLINE_VISIBILITY _Tp* release() throw()
{
_Tp* __t = __ptr_;
__ptr_ = 0;
return __t;
}
它把原来智能指针的成员__ptr_设置为空指针,再把原来指针返回,而
auto_ptr(
auto_ptr
<_Up>& __p) 这个构造函数就把返回的指针传给自己的成员__ptr_。
上面的智能指针使用了栈中创建的对象,编译器自动在函数结束为我们生成析构函数的调用,然后在智能指针的析构中删除动态创建的对象,所以我们不需要手动编码delete了。根据下面代码:
template<class _Up> _LIBCPP_INLINE_VISIBILITY auto_ptr(auto_ptr<_Up>& __p) throw()
: __ptr_(__p.release()) {}
可以发现原型只能被一个智能指针保持,之前智能指针的成员指针赋值为0,再把返回的赋值给新的智能指针。查看下面代码:
#include <memory>
using namespace std;
class Cls{
public:
virtual ~Cls(){ printf("Cls Destructor is called\n"); }
};
class SubCls: public Cls{
public:
int a = 5;
virtual ~SubCls(){ printf("SubCls Destructor is called\n"); }
};
void testAutoptr(){
auto_ptr<SubCls> obj1(new SubCls);
// auto_ptr<Cls> obj2(obj1);
auto_ptr<SubCls> obj2 = obj1;
obj1->a = 4;
}
最后程序会停在
obj1->
a
=
4
;出现exe_bad_access的错误,这是因为obj1存储的原型是空指针了。
上面还看不出什么作用,作用域小,只能管理一个对象。可以稍微改一下,想象一下,我们要管理一组对象,我们不想手动delete它,我们把它们的指针存在一个管理对象中,让这个对象作用域变大,我们不在栈中而在堆中创建这个管理对象,这样我们可以在需要的时候delete它,并在它的析构函数里面删除那些接受管理的对象。下面是一个例子:
#include <stdio.h>
#include <memory>
#include <vector>
using namespace std;
class Obj{
public:
~Obj(){ printf("Destructor is called\n"); }
};
class ObjManager{
public:
void addObject(auto_ptr<Obj>* obj){ m_objects.push_back(obj); }
~ObjManager(){
for (vector<auto_ptr<Obj>*>::iterator iter = m_objects.begin(); iter!=m_objects.end(); iter++) {
delete *iter;
}
}
private:
vector<auto_ptr<Obj>*> m_objects;
};
void testAutoptr2(){
auto sprite1 = new auto_ptr<Obj>(new Obj);
auto sprite2 = new auto_ptr<Obj>(new Obj);
auto sprite3 = new auto_ptr<Obj>(new Obj);
auto sprite4 = new auto_ptr<Obj>(new Obj);
ObjManager *om = new ObjManager;
om->addObject(sprite1);
om->addObject(sprite2);
om->addObject(sprite3);
om->addObject(sprite4);
delete om;
}
输出:
Destructor is called
Destructor is called
Destructor is called
Destructor is called
Program ended with exit code: 0
现在可以管理N个对象了,这里可以把
auto
sprite1 =
new
auto_ptr
<
Obj
>(
new
Obj
); om->addObject(sprite1);包装一下,如下:
#include <stdio.h>
#include <memory>
#include <vector>
using namespace std;
class Obj;
class ObjManager{
public:
static ObjManager* getInstance(){
static ObjManager *ret = NULL;
if (!ret) {
ret = new ObjManager;
}
return ret;
}
void addObject(auto_ptr<Obj>* obj){ m_objects.push_back(obj); }
void release(){
for (vector<auto_ptr<Obj>*>::iterator iter = m_objects.begin(); iter!=m_objects.end(); iter++) {
delete *iter;
}
}
private:
vector<auto_ptr<Obj>*> m_objects;
};
class Obj{
public:
static auto_ptr<Obj>* create(){
auto_ptr<Obj> *ret = new auto_ptr<Obj>(new Obj);
ObjManager::getInstance()->addObject(ret);
return ret;
}
~Obj(){ printf("Destructor is called\n"); }
};
void testAutoptr2(){
auto sprite1 = Obj::create();
auto sprite2 = Obj::create();
auto sprite3 = Obj::create();
auto sprite4 = Obj::create();
ObjManager::getInstance()->release();
}
上面有点cocos的样子了,但是问题还是比较明显的,创建的对象只加入到了对象管理器,没加到其它的对象中,比如父结点什么的,我们发现上面智能指针只能被一个对象用,我们根本没法判断什么时候可以调用
ObjManager
::
getInstance
()->
release
();。我推荐智能指针只在某个函数中用用,最后借助它删除动态创建的对象,减去手动编码问题,如果使用手动的删除,可能会忘了,最后造成内存泄漏。小程序用不到什么内存管理,那些忘记手动删除的容易发现,但是大型程序必须要有一种机制杜绝手动删除,建立内存管理。其中建立如上的内存管理中心,在某个合适的时候释放那些动态分配的内存。有的程序右下角有个内存清除、功能,估计就是这个原理。有时间好好研究下大型程序下的内存管理。
2、引用计数
假如一个对象被多个对象引用,如果使用智能指针管理,那就会出现智能指针释放了指向的对象,但是还有对象被使用的情况,下面代码避免了这个情况:
#include <stdio.h>
//#include <memory>
#include <vector>
//using namespace std;
template<class T>
class auto_ptr
{
private:
T* __ptr_;
public:
explicit auto_ptr(T* __p = 0) throw() : __ptr_(__p) {}
auto_ptr(auto_ptr& __p) throw() : __ptr_(__p.release()) {}
template<class _Up> auto_ptr(auto_ptr<_Up>& __p) throw()
: __ptr_(__p.release()) {}
auto_ptr& operator=(auto_ptr& __p) throw()
{reset(__p.release()); return *this;}
template<class _Up> auto_ptr& operator=(auto_ptr<_Up>& __p) throw()
{reset(__p.release()); return *this;}
~auto_ptr() throw() {if(--__ptr_->referenceCount == 0) delete __ptr_;}//减去一个计数,计数为0删除
T& operator*() const throw()
{return *__ptr_;}
T* operator->() const throw() {return __ptr_;}
T* get() const throw() {return __ptr_;}
T* release() throw()
{
T* __t = __ptr_;
__ptr_ = 0;
return __t;
}
void reset(T* __p = 0) throw()
{
if (__ptr_ != __p)
delete __ptr_;
__ptr_ = __p;
}
template<class _Up> operator auto_ptr<_Up>() throw()
{return auto_ptr<_Up>(release());}
};
class Obj;
class ObjManager{
public:
static ObjManager* getInstance(){
static ObjManager *ret = NULL;
if (!ret) {
ret = new ObjManager;
}
return ret;
}
void addObject(auto_ptr<Obj>* obj){ m_objects.push_back(obj); }
void release(){
for (std::vector<auto_ptr<Obj>*>::iterator iter = m_objects.begin(); iter!=m_objects.end(); iter++) {
delete *iter;
}
}
private:
std::vector<auto_ptr<Obj>*> m_objects;
};
class Obj{
public:
Obj():referenceCount(1){}
static auto_ptr<Obj>* create(){
auto_ptr<Obj> *ret = new auto_ptr<Obj>(new Obj);
ObjManager::getInstance()->addObject(ret);
return ret;
}
void retain(){ ++referenceCount;}
void release(){ --referenceCount;}
~Obj(){ printf("Destructor is called\n"); }
int referenceCount;//使用计数
};
void testAutoptr2(){
auto sprite1 = Obj::create();
auto sprite2 = Obj::create();
auto sprite3 = Obj::create();
auto sprite4 = Obj::create();
(*sprite1)->retain();
ObjManager::getInstance()->release();
}
输出:
Destructor is called
Destructor is called
Destructor is called
Program ended with exit code: 0
上面由于
(*sprite1)->
retain
();使计数加一,最后没能删除对象。
上面使用了我修改了的auto_ptr版本,
~auto_ptr()
throw
() {
if
(--
__ptr_
->referenceCount ==
0
)
delete
__ptr_
;}析构函数根据对象的计数,决定是否删除。
T* release() throw() { return__ptr_; }不再释放指向的对象,这里释放用了引用计数技术。现在代码可以在任何地方动态创建对象,以及传递对象给其它智能指针,同时记得要retain下,释放时要release。这里又有问题了,刚拜托不用手动delete,现在又要retain与release了,显然不行。其实这两个接口不是给客户直接使用的,而是用于api开发,对于那些管理对象的类使用的,那些管理类可以把对象加进来,对它们计数加一,这时计数为2,当管理类释放内存时,对管理的对象调用release,减小计数,如果计数为0就释放那个对象。而如果对象在其它对象中,那么它的计数也加一,当其它对象析构时,会调用此对象的release,计数减1,为0释放。
现在去除智能指针,因为它的作用仅仅是替代手写delete,我们用了引用计数技术,就不需要它了,改善后代码如下:
#include <stdio.h>
#include <vector>
#include <assert.h>
class Obj{
public:
Obj();
static Obj* create();
void retain();
void release();
virtual ~Obj();
private:
int referenceCount;//使用引用计数
};
class ObjManager{
public:
static ObjManager* getInstance(){
static ObjManager *ret = NULL;
if (!ret) {
ret = new ObjManager;
}
return ret;
}
void addObject(Obj *obj){ m_objects.push_back(obj); }
void release(){
for (std::vector<Obj *>::iterator iter = m_objects.begin(); iter!=m_objects.end(); iter++) {
(*iter)->release();
}
}
private:
std::vector<Obj *> m_objects;
};
Obj::Obj():referenceCount(1){}
Obj* Obj::create(){
Obj *ret = new Obj;
ObjManager::getInstance()->addObject(ret);
return ret;
}
void Obj::retain(){ ++referenceCount;}
void Obj::release(){
assert(referenceCount > 0);
--referenceCount;
if (referenceCount == 0) {
delete this;
}
}
Obj::~Obj(){ printf("Destructor is called\n"); }
void testAutoptr2(){
Obj* sprite1 = Obj::create();
Obj* sprite2 = Obj::create();
Obj* sprite3 = Obj::create();
Obj* sprite4 = Obj::create();
Obj* m1 = sprite1;
sprite1->retain();
ObjManager::getInstance()->release();
}
输出:
Destructor is called
Destructor is called
Destructor is called
Program ended with exit code: 0
在我们赋值
Obj
* m1 = sprite1;完后,要记得sprite1->retain();。最后sprite1计数为1没有得到释放。
二、cocos的内存管理——引用计数
1、引用计数的支持接口
上面使用了引用计数的计数,只是稍微演示了一下,并没有进行详细的对象设计,也没有将到底在哪里使用。是的,不同的内存技术不是在任何场景下都合适的,下面介绍cocos的,它用了引用计数,而且这种引用计数对于它特比合适。下面是代码分析:
class CC_DLL CCCopying
{
public:
virtual CCObject* copyWithZone(CCZone* pZone);
};
/**
* @js NA
*/
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;
};
上面CCObject是cocos其它类的父类。里面有
void
release(
void
);
void
retain(
void
);
CCObject
* autorelease(
void
);这三个函数,前两个上面介绍过,autorelease是把对象加到管理池中跟我们上面的
void
addObject(
Obj
*obj){
m_objects
.
push_back
(obj); }有相同的功能。void release(void);void retain(void);CCObject* autorelease(void)代码如下:
void CCObject::release(void)
{
CCAssert(m_uReference >0,"reference count should greater than 0");
--m_uReference;
if (m_uReference ==0)
{
deletethis;
}
}
void CCObject::retain(void)
{
CCAssert(m_uReference >0,"reference count should greater than 0");
++m_uReference;
}
CCObject* CCObject::autorelease(void)
{
CCPoolManager::sharedPoolManager()->addObject(this);
returnthis;
}
上面代码跟前面介绍的引用计数的3个函数一样。
Obj* Obj::create(){
Obj *ret = new Obj;
ObjManager::getInstance()->addObject(ret);
return ret;
}
我们使用上面函数把对象加入管理池的,这里是使用
CCObject
*
CCObject
::autorelease(
void
),里面调用了CCPoolManager::sharedPoolManager()->addObject(this);,把对象交由CCPoolManager管理。CCPoolManager::sharedPoolManager()->addObject(this)代码如下:
void CCPoolManager::addObject(CCObject* pObject)
{
getCurReleasePool()->addObject(pObject);
}
获得CCPoolManager管理器的一个自动释放池,CCPoolManager里面有个数组充当释放池栈,上面代码把对象加入到当前自动释放池。
getCurReleasePool
()->
addObject
(pObject);代码如下:
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.
}
上面代码比较奇怪的是 pObject-> release ();,计数为1不就边0释放对象了吗?查看m_pManagedObjectArray->addObject(pObject);代码如下:
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++;
}
上面代码发现对象加入数组被retain了,所以需要
pObject->
release
()把计数保持在1。
上面是引用计数的支持接口,可以通过retain、release操作计数,通过autorelease加入管理池。
2、CCPoolManager如何管理对象的
bool CCDirector::init(void)
{
setDefaultValues();
// scenes
m_pRunningScene = NULL;
m_pNextScene = NULL;
m_pNotificationNode = NULL;
m_pobScenesStack = new CCArray();
m_pobScenesStack->init();
// projection delegate if "Custom" projection is used
m_pProjectionDelegate = NULL;
// FPS
m_fAccumDt = 0.0f;
m_fFrameRate = 0.0f;
m_pFPSLabel = NULL;
m_pSPFLabel = NULL;
m_pDrawsLabel = NULL;
m_uTotalFrames = m_uFrames = 0;
m_pszFPS = new char[10];
m_pLastUpdate = new struct cc_timeval();
// paused ?
m_bPaused = false;
// purge ?
m_bPurgeDirecotorInNextLoop = false;
m_obWinSizeInPoints = CCSizeZero;
m_pobOpenGLView = NULL;
m_fContentScaleFactor = 1.0f;
// scheduler
m_pScheduler = new CCScheduler();
// action manager
m_pActionManager = new CCActionManager();
m_pScheduler->scheduleUpdateForTarget(m_pActionManager, kCCPrioritySystem, false);
// touchDispatcher
m_pTouchDispatcher = new CCTouchDispatcher();
m_pTouchDispatcher->init();
// KeypadDispatcher
m_pKeypadDispatcher = new CCKeypadDispatcher();
// Accelerometer
m_pAccelerometer = new CCAccelerometer();
// create autorelease pool
CCPoolManager::sharedPoolManager()->push();
return true;
}
上面
CCPoolManager
::
sharedPoolManager
()->
push
();创建了管理池,push代码如下:
void CCPoolManager::push()
{
CCAutoreleasePool* pPool = new CCAutoreleasePool(); //ref = 1
m_pCurReleasePool = pPool;
m_pReleasePoolStack->addObject(pPool); //ref = 2
pPool->release(); //ref = 1
}
创建一个CCAutoreleasePool作为当前释放池m_pCurReleasePool,最后加入
CCArray
* m_pReleasePoolStack这个用数组模拟的栈中。现在CCPoolManager创建好了,并且在m_pReleasePoolStack放入了一个自动释放池,这个池子管理对象内存。下面是每帧的处理代码:
void CCDisplayLinkDirector::mainLoop(void)
{
if (m_bPurgeDirecotorInNextLoop)
{
m_bPurgeDirecotorInNextLoop = false;
purgeDirector();
}
else if (! m_bInvalid)
{
drawScene();
// release the objects
CCPoolManager::sharedPoolManager()->pop();
}
}
drawScene显示完一帧后,调用
CCPoolManager
::
sharedPoolManager
()->
pop
();代码如下:
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_pCurReleasePool->clear();代码就是清除释放池,如果m_pReleasePoolStack栈中存在不少于2个的自动释放池,就把当前栈顶下移,下移就是把数组的元素减一,
研究发现根本用不到两个池子,一个池子就够了,正常情况池子数一直未1。
m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount - 2)就是把m_pCurReleasePool向前移一个元素,指向新的栈顶。
m_pCurReleasePool->clear();代码如下:
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();
}
}
上面代码当
m_pManagedObjectArray(CCArray* )这个存放对象指针的数组元素个数大于0时,会调用
m_pManagedObjectArray
->
removeAllObjects
();移除所有对象。
CCARRAY_FOREACH_REVERSE
(
m_pManagedObjectArray
, pObj)对m_uAutoReleaseCount减一,查看因为可能对象在自动释放池中多次被管理,移除对象时需要根据m_uAutoReleaseCount调用多次release,
void
CCAutoreleasePool
::removeObject(
CCObject
* pObject)中执行了这些操作,至于 CCAutoreleasePool::removeObject哪里用到,没有发现。
m_pManagedObjectArray
->
removeAllObjects
();的代码如下:
void CCArray::removeAllObjects()
{
ccArrayRemoveAllObjects(data);
}
void ccArrayRemoveAllObjects(ccArray *arr)
{
while( arr->num > 0 )
{
(arr->arr[--arr->num])->release();
}
}
最后会调用被管理对象的release()方法使计数减一。
3、CCObject如何把自己加入到CCPoolManager中接受管理的
我们发现了CCObject的派生对象计数为1,调用了autorelease后计数仍保持1,然后mainloop绘制一帧后,执行内存释放,把CCPoolManager的m_pCurReleasePool清除干净,对所有在m_pCurReleasePool中的指针指向的对象计数减一,这个时候对象计数为0的就被释放了。所以你create了对象后,如果没有retain,或者没加到其它对象上,下一帧渲染后,对象就被释放了。
CCObject* CCObject::autorelease(void)
{
CCPoolManager::sharedPoolManager()->addObject(this);
return this;
}
<pre name="code" class="cpp">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;
}
当调用autorelease()时会向CCPoolManager的当前释放池添加一个被管理的对象。它会检查释放池是否存在,不存在就建一个作为当前释放池,然后加入到被管理的对象。
4、CCObject不再接受CCPoolManager管理后,如何被删除的
现在理一下,autorelease()是把CCObject的派生类对象加入当前释放池m_pCurReleasePool,它使用了CCPoolManager的addObject接口,这个接口是CCPoolManager向外界提供的添加被管理对象的接口。你可以使用addObject把对象交由CCPoolManager管理。CCPoolManager内部有一个释放池栈,在CCDirector的构造函数里通过CCPoolManager的push函数创建了一个释放池,m_pCurReleasePool指向这个池子。之后所有通过autorelease()想让CCPoolManager管理内存的对象的指针都有m_pCurReleasePool保管。在每帧的mainloop被调用后,也就是先绘制一个drawScene后,调用CCPoolManager的pop函数,池子只有1个,栈顶永远不会弹出,此时m_pCurReleasePool会调用每个存储的指针指向的被管理对象的release函数,对它们计数减一,如果计数为0了,说明没对象引用当前对象了,就把它释放掉。你创建的对象不立马(在CCPoolManager::sharedPoolManager()->pop()之前,drawScene()的时候)加到其它节点上,或者不retain,等到CCPoolManager::sharedPoolManager()->pop()的时候对象就会被释放了。你那些通过addChild加入到父节点上的节点,它们计数为2,CCPoolManager::sharedPoolManager()->pop()后计数为1,这个时候保存它指针的m_pCurReleasePool的大小为0(保存对象指针的数组大小),也就是不在受CCPoolManager管理了。那它计数为1不就释放不了了?不是的,它可以通过removeFromParentWithCleanup进行删除,代码如下:
void CCNode::removeFromParentAndCleanup(bool cleanup)
{
if (m_pParent != NULL)
{
m_pParent->removeChild(this,cleanup);
}
}
void CCNode::removeChild(CCNode* child, bool cleanup)
{
// explicit nil handling
if (m_pChildren == NULL)
{
return;
}
if ( m_pChildren->containsObject(child) )
{
this->detachChild(child,cleanup);
}
}
void CCNode::detachChild(CCNode *child, bool doCleanup)
{
// IMPORTANT:
// -1st do onExit
// -2nd cleanup
if (m_bRunning)
{
child->onExitTransitionDidStart();
child->onExit();
}
// If you don't do cleanup, the child's actions will not get removed and the
// its scheduledSelectors_ dict will not get released!
if (doCleanup)
{
child->cleanup();
}
// set parent nil at the end
child->setParent(NULL);
m_pChildren->removeObject(child);
}
最后的
m_pChildren
->
removeObject
(child);是核心。A要从B中释放,A调用removeFromParentWithCleanup,removeFromParentWithCleanup会调用B的
CCNode::removeChild(CCNode* child,bool cleanup),注意这里是B(父节点)的方法。 m_pChildren->removeObject(child)代码如下:
void CCArray::removeObject(CCObject* object, bool bReleaseObj/* = true*/)
{
ccArrayRemoveObject(data, object, bReleaseObj);
}
void ccArrayRemoveObject(ccArray *arr, CCObject* object, bool bReleaseObj/* = true*/)
{
unsigned int index = ccArrayGetIndexOfObject(arr, object);
if (index != CC_INVALID_INDEX)
{
ccArrayRemoveObjectAtIndex(arr, index, bReleaseObj);
}
}
void ccArrayRemoveObjectAtIndex(ccArray *arr, unsigned int index, bool bReleaseObj/* = true*/)
{
CCAssert(arr && arr->num > 0 && index < arr->num, "Invalid index. Out of bounds");
if (bReleaseObj)
{
CC_SAFE_RELEASE(arr->arr[index]);
}
arr->num--;
unsigned int remaining = arr->num - index;
if(remaining>0)
{
memmove((void *)&arr->arr[index], (void *)&arr->arr[index+1], remaining * sizeof(CCObject*));
}
}
上面的
if
(bReleaseObj)CC_SAFE_RELEASE(arr->arr[index]);,它对节点计数减一,所以此时计数为0,就被删除了。它还可以通过显式release,不过直接这样做是错误的,只有你手动retain()了一下,最后想释放时才需要release()一下。计数为1不再被CCPoolManager管理,除了removeFromParentWithCleanup可以从父节点中移除并把直接删除,还可以通过父节点的删除来移除自己。父节点计数为0删除时会调用父节点的析构函数,我们跟踪下这个函数,相关代码如下:
CCNode::~CCNode(void)
{
CCLOGINFO( "cocos2d: deallocing" );
unregisterScriptHandler();
if (m_nUpdateScriptHandler)
{
CCScriptEngineManager::sharedManager()->getScriptEngine()->removeScriptHandler(m_nUpdateScriptHandler);
}
CC_SAFE_RELEASE(m_pActionManager);
CC_SAFE_RELEASE(m_pScheduler);
// attributes
CC_SAFE_RELEASE(m_pCamera);
CC_SAFE_RELEASE(m_pGrid);
CC_SAFE_RELEASE(m_pShaderProgram);
CC_SAFE_RELEASE(m_pUserObject);
if(m_pChildren && m_pChildren->count() > 0)
{
CCObject* child;
CCARRAY_FOREACH(m_pChildren, child)
{
CCNode* pChild = (CCNode*) child;
if (pChild)
{
pChild->m_pParent = NULL;
}
}
}
// children
CC_SAFE_RELEASE(m_pChildren);
// m_pComsContainer
m_pComponentContainer->removeAll();
CC_SAFE_DELETE(m_pComponentContainer);
}
上面 CC_SAFE_RELEASE(m_pChildren);是关键,m_pChildren类型是CCArray *,CC_SAFE_RELEASE代码如下:
#define CC_SAFE_RELEASE(p) do { if(p) { (p)->release(); } } while(0)
m_pChildren的release()会被调用,计数为0此时m_pChildren会被删除,它的析构函数会被调用,查看CCArray析构如下:
CCArray::~CCArray()
{
ccArrayFree(data);
}
void ccArrayFree(ccArray*& arr)
{
if( arr == NULL )
{
return;
}
ccArrayRemoveAllObjects(arr);
free(arr->arr);
free(arr);
arr = NULL;
}
void ccArrayRemoveAllObjects(ccArray *arr)
{
while( arr->num > 0 )
{
(arr->arr[--arr->num])->release();
}
}
(arr->arr[--arr->num])->release()的执行说明了数组中的指向的对象的计数都会减一,父节点的删除调用了存储孩子的数组的析构函数,数组的析构函数调用对每个指向的对象计数减一,此时那些计数为1的孩子节点计数变为0,被释放。
5、特殊节点CCScene怎么被销毁和存在的
上面讲了一个节点怎么被销毁的了,再看下场景这个节点怎么被销毁的,它不是我们直接remove就可以删除的,cocos没这个接口,想想一下应该是runWithScene与replaceScene这两个函数,跟踪代码如下:
void CCDirector::replaceScene(CCScene *pScene)
{
CCAssert(m_pRunningScene, "Use runWithScene: instead to start the director");
CCAssert(pScene != NULL, "the scene should not be null");
unsigned int index = m_pobScenesStack->count();
m_bSendCleanupToScene = true;
m_pobScenesStack->replaceObjectAtIndex(index - 1, pScene);
m_pNextScene = pScene;
}
上面
m_pobScenesStack
->
replaceObjectAtIndex
(index -
1
, pScene);会把之前场节点景计数减一,新的场景节点计数加一,具体可以继续跟踪下这个函数,这里不再贴代码。
void CCDirector::runWithScene(CCScene *pScene)
{
CCAssert(pScene != NULL, "This command can only be used to start the CCDirector. There is already a scene present.");
CCAssert(m_pRunningScene == NULL, "m_pRunningScene should be null");
pushScene(pScene);
startAnimation();
}
void CCDirector::pushScene(CCScene *pScene)
{
CCAssert(pScene, "the scene should not null");
m_bSendCleanupToScene = false;
m_pobScenesStack->addObject(pScene);
m_pNextScene = pScene;
}
m_pobScenesStack->addObject(pScene);把加入的场景节点计数加一。runWithScene与replaceScene的区别上面也可以看出来。一个初始化空栈(一个数组),一个插入当前场景再删除之前场景。: