您的位置:首页 > 移动开发 > Cocos引擎

cocos2dx-内存管理剖析(智能指针的局限与引用计数的选择)

2016-06-25 00:48 483 查看
一、常用内存管理计数

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 = newauto_ptr<Obj>(newObj);
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,voidCCAutoreleasePool::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的区别上面也可以看出来。一个初始化空栈(一个数组),一个插入当前场景再删除之前场景。:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: