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

一、常用内存管理技术

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的区别上面也可以看出来。一个初始化空栈(一个数组),一个插入当前场景再删除之前场景。




















































  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值