cocos2dx-内存管理剖析(智能指针的局限与引用计数的选择)
2016-06-25 00:48
483 查看
一、常用内存管理计数
delete __ptr_把__ptr_指向的对象obj给delete了,obj的析构函数接着被调用。auto_ptr<Obj>的构造函数如下:
_Up> _LIBCPP_INLINE_VISIBILITY auto_ptr(auto_ptr<_Up>& __p)throw() :__ptr_(__p.release())
{}这段代码里调用了__p.release(),查看如下:
上面的智能指针使用了栈中创建的对象,编译器自动在函数结束为我们生成析构函数的调用,然后在智能指针的析构中删除动态创建的对象,所以我们不需要手动编码delete了。根据下面代码:
上面还看不出什么作用,作用域小,只能管理一个对象。可以稍微改一下,想象一下,我们要管理一组对象,我们不想手动delete它,我们把它们的指针存在一个管理对象中,让这个对象作用域变大,我们不在栈中而在堆中创建这个管理对象,这样我们可以在需要的时候delete它,并在它的析构函数里面删除那些接受管理的对象。下面是一个例子:
om->addObject(sprite1);包装一下,如下:
输出:
上面使用了我修改了的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,我们用了引用计数技术,就不需要它了,改善后代码如下:
二、cocos的内存管理——引用计数
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个函数一样。
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);代码如下:
上面是引用计数的支持接口,可以通过retain、release操作计数,通过autorelease加入管理池。
m_pCurReleasePool->clear();代码就是清除释放池,如果m_pReleasePoolStack栈中存在不少于2个的自动释放池,就把当前栈顶下移,下移就是把数组的元素减一,
研究发现根本用不到两个池子,一个池子就够了,正常情况池子数一直未1。
m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount
- 2)就是把m_pCurReleasePool向前移一个元素,指向新的栈顶。
m_pCurReleasePool->clear();代码如下:
上面代码当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();的代码如下:
当调用autorelease()时会向CCPoolManager的当前释放池添加一个被管理的对象。它会检查释放池是否存在,不存在就建一个作为当前释放池,然后加入到被管理的对象。
CCNode::removeChild(CCNode* child,
bool cleanup),注意这里是B(父节点)的方法。 m_pChildren->removeObject(child)代码如下:
上面 CC_SAFE_RELEASE(m_pChildren);是关键,m_pChildren类型是CCArray *,CC_SAFE_RELEASE代码如下:
(arr->arr[--arr->num])->release()的执行说明了数组中的指向的对象的计数都会减一,父节点的删除调用了存储孩子的数组的析构函数,数组的析构函数调用对每个指向的对象计数减一,此时那些计数为1的孩子节点计数变为0,被释放。
1, pScene);会把之前场节点景计数减一,新的场景节点计数加一,具体可以继续跟踪下这个函数,这里不再贴代码。
m_pobScenesStack->addObject(pScene);把加入的场景节点计数加一。runWithScene与replaceScene的区别上面也可以看出来。一个初始化空栈(一个数组),一个插入当前场景再删除之前场景。:
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的区别上面也可以看出来。一个初始化空栈(一个数组),一个插入当前场景再删除之前场景。:
相关文章推荐
- CVP认证学习笔记--李天宇010节点的计划任务
- CVP认证学习笔记--李天宇009节点的缩放和旋转
- CVP认证学习笔记--李天宇007节点的锚点和坐标
- CVP认证学习笔记--李天宇006场景切换特效
- CVP认证学习笔记--李天宇005实现多场景切换
- CVP认证学习笔记--李天宇004实现自定义场景
- CVP认证学习笔记--李天宇003实现文本菜单层
- CVP认证学习笔记--李天宇002添加图片层
- CVP认证学习笔记--李天宇001添加文字层
- 7------cocos2dx 3.1.1 在线热更新 自动更新(AssetsManager)
- 6------cocos2dx-------3.3<LUA>官网英文版
- 5------Cocos2dx-- 资源热更新(lua)
- cocos2dx-3.10 开发初探(一)
- cocos2dx使用xxtea加密资源
- cocos2dx中zip包读取解压使用
- cocos2dx报错记录
- android stadio2.0 配置cocos2d-x-3.11.1 成功
- cocos-lua问题记录
- cocos-lua-通知节点
- Cocos2d-x微信登陆Demo