Cocos2d-x 内存管理剖析(1)

下面这篇文章将深入cocos2dx引擎的源代码,剖析cocos2dx中内存的管理机制的实现。使用cocos2dx-2.1.5版本。

参考文章:点击打开链接 

一、前言:

我们都知道在c++中,创建实例化的对象都是使用new关键字,在不使用或者不需要的时候必须手动使用delete关键字去掉这个对象,否则new出来的对象就不能回收,造成内存泄露。


例如(我们创建了一个大小为10的数组)

int* p = new int[10];
    for (int i = 0; i<10; i++) {
        p[i] = i;
    }

如果使用结束后,不手动  delete [] p; 那么就会造成这个数组的内存泄露。


二、Cocos2d-x采用引用计数方式对对象进行内存管理

关于引用计数方式的内存管理,熟悉ios开发的人来说应该不陌生;其内存管理的过程大致是:为每一个对象保存一个引用计数,当引用计数大于0的时候,对象继续保存在内存中;但是当引用计数等于0的时候,自动释放这个对象,这个过程就是autorelease(自动管理)。


在Cocos2d-x中,CCObject是整个引擎的基础类,其他的类都是继承自这个类而来。


那么,首先从这个类开始进行内存管理的剖析:

在 libs/cocos2dx/cocoa/ 位置下找到这个类文件。

class CC_DLL CCObject : public CCCopying
{
public:
    // object id, CCScriptSupport need public m_uID
    unsigned int        m_uID;
    // Lua reference id
    int                 m_nLuaID;
protected:
    // count of references
    unsigned int        m_uReference;
    // count of autorelease
    unsigned int        m_uAutoReleaseCount;
public:
    CCObject(void);
    virtual ~CCObject(void);
    
    void release(void);
    void retain(void);
    CCObject* autorelease(void);
    CCObject* copy(void);
    bool isSingleReference(void) const;
    unsigned int retainCount(void) const;
    virtual bool isEqual(const CCObject* pObject);

    virtual void acceptVisitor(CCDataVisitor &visitor);

    virtual void update(float dt) {CC_UNUSED_PARAM(dt);};
    
    friend class CCAutoreleasePool;
};
说明:

public 中的两个属性:对象的ID编号(其中第二个是用于Lua中的);


protected 中的两个属性:

  m_uReference表示对象的引用计数;

  m_uAutoReleaseCount 则表示对象是否自动管理,也就是表示是否autorelease。注意到 m_uAutoReleaseCount 这个变量是unsigned int类型,在之前一些版本中,曾经是使用 bool m_bManaged 这个bool变量,其实本质是一样的,如果对象autorelease(自动管理),那么 ++m_uAutoReleaseCount,只要这个值非零,那么这个对象就是autorelease的。而假如是bool变量的话,那么就是让其变成true就是autorelease了。


前面说到,对对象的内存管理是使用到引用计数,那么我们就看看CCObject中,引用计数 m_uReference 是如何变化的吧!

首先我们进入CCObject的构造方法中:

CCObject::CCObject(void)
: m_nLuaID(0)
, m_uReference(1) // when the object is created, the reference count of it is 1
, m_uAutoReleaseCount(0)
{
    static unsigned int uObjectCount = 0;

    m_uID = ++uObjectCount;
}

显然,创建一个CCObject对象之后,其引用计数 m_uReference 变为1,而 m_uAutoReleaseCount 为0,表示对象还没有autorelease。


接着就是retain和release方法了,前者对引用计数加1,后者引用计数减1。

void CCObject::release(void)
{
    CCAssert(m_uReference > 0, "reference count should greater than 0");
    --m_uReference;

    if (m_uReference == 0)
    {
        delete this;
    }
}

void CCObject::retain(void)
{
    CCAssert(m_uReference > 0, "reference count should greater than 0");

    ++m_uReference;
}

其中release方法中,如果引用计数 m_uReference 为0,那么就自动删除这个对象进行回收。

好了,通过上面的介绍我们就可以知道cocos2dx中的引用计数是怎么一回事了,接下来就是 :引擎中是如何对实例对象进行自动管理。



三、实例对象自动管理autorelease

下面从一个CCNode创建的生命历程来讲一下这个自动管理的过程。

在 libs/cocos2dx/base_nodes 中找到CCNode的类文件。

进入其中的create方法。

CCNode * CCNode::create(void)
{
	CCNode * pRet = new CCNode();
    if (pRet && pRet->init())
    {
        pRet->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(pRet);
    }
	return pRet;
}

其中 create 创建的过程是先用 new创建对象,然后将对象自动管理 autorelease。(所以我们就知道了,我们一般创建节点对象都使用create,其过程就是一个 new+autorelease = create


进入其中的autorelease方法

CCObject* CCObject::autorelease(void)
{
    CCPoolManager::sharedPoolManager()->addObject(this);
    return this;
}
进入其中的addObject方法

void CCPoolManager::addObject(CCObject* pObject)
{
    getCurReleasePool()->addObject(pObject);
}
再进入其中的addObject方法

void CCAutoreleasePool::addObject(CCObject* pObject)
{
    m_pManagedObjectArray->addObject(pObject);

    CCAssert(pObject->m_uReference > 1, "reference count should be greater than 1");
    ++(pObject->m_uAutoReleaseCount);
    pObject->release(); // no ref count, in this case autorelease pool added.
}

首先看一下在这个过程中我们遇到了这两个类:CCPoolManager 和 CCAutoreleasePool,前者是对象自动管理类,后者是对象自动管理(释放)池。后面再详细分析一下这个autorelease过程。


先看到CCAutoreleasePool这个类:

class CC_DLL CCAutoreleasePool : public CCObject
{
    CCArray*    m_pManagedObjectArray;    
public:
    CCAutoreleasePool(void);
    ~CCAutoreleasePool(void);

    void addObject(CCObject *pObject);
    void removeObject(CCObject *pObject);

    void clear();
};
类中有一个  m_pManagedObjectArray  数组,我们将对象自动管理后,都是将对象添加到这个数组中的,所以说是对象自动管理(释放)池。值得注意的是,这个类也是继承自CCObject,那么显然其内存管理形式也采用引用计数的。


其中的addObject方法就是将需要自动管理的对象添加到这个m_pManagedObjectArray数组中,那么就说将对象进行了自动管理autorelease。


下面进入这个方法看看:

void CCAutoreleasePool::addObject(CCObject* pObject)
{
    m_pManagedObjectArray->addObject(pObject);

    CCAssert(pObject->m_uReference > 1, "reference count should be greater than 1");
    ++(pObject->m_uAutoReleaseCount);
    pObject->release(); // no ref count, in this case autorelease pool added.
}

首先是将对象添加到数组中,然后将对象自动管理变量++,那么对象的 m_uAutoReleaseCount =1,也就是表明其为自动管理对象。


然后对对象进行release,也即对对象的引用计数进行减1的操作,这个是为什么呢?


下面分析一个这个原因:

①首先一个CCNode对象被创建,使用的是new,也就是调用了构造方法,那么其引用计数 m_uReference=1。这个应该不难理解吧:CCNode是继承自CCObject,而在前面CCObject的构造方法中就知道,new创建一个实例对象后,其引用计数m_uReference=1,而是否自动管理变量 m_uAutoReleaseCount=0(表示对象未添加到自动管理(释放)池中自动管理)。


②接着对象autorelease,那么就是将对象添加到自动释放池中,而其中的:

m_pManagedObjectArray->addObject(pObject); 将被管理对象添加到自动管理池中的过程中(将对象添加到数组中),其会对对象进行retain的,所以 m_uReference 就变成了 m_uReference=2。


下面跟踪一下这个过程:

void CCArray::addObject(CCObject* object)
{
    ccArrayAppendObjectWithResize(data, object);
}

/** Appends an object. Capacity of arr is increased if needed. */
void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object)
{
	ccArrayEnsureExtraCapacity(arr, 1);
	ccArrayAppendObject(arr, object);
}

/** Appends an object. Behavior undefined if array doesn't have enough capacity. */
void ccArrayAppendObject(ccArray *arr, CCObject* object)
{
    CCAssert(object != NULL, "Invalid parameter!");
    object->retain();
	arr->arr[arr->num] = object;
	arr->num++;
}

看到最后方法中 object->retain(); 了吧!


③所以最后的release是为了将引用计数重新变成为1。这样的话,从对象创建,到被放到对象自动管理池,引用计数依然为1,被管理值也为1(表征对象是自动管理)。



下面看看 CCPoolManager 类,这个类顾名思义就知道是对对象自动释放池进行管理的类,它是一个单例类。

class CC_DLL CCPoolManager
{
    CCArray*    m_pReleasePoolStack;    
    CCAutoreleasePool*                    m_pCurReleasePool;

    CCAutoreleasePool* getCurReleasePool();
public:
    CCPoolManager();
    ~CCPoolManager();
    void finalize();
    void push();
    void pop();

    void removeObject(CCObject* pObject);
    void addObject(CCObject* pObject);

    static CCPoolManager* sharedPoolManager();
    static void purgePoolManager();

    friend class CCAutoreleasePool;
};
这个类中的属性  m_pCurReleasePool 表示当前的自动释放池,可以使用  getCurReleasePool()这个方法获取到当前的这个属性,进入这个方法:


CCAutoreleasePool* CCPoolManager::getCurReleasePool()
{
    if(!m_pCurReleasePool)
    {
        push();
    }

    CCAssert(m_pCurReleasePool, "current auto release pool should not be null");

    return m_pCurReleasePool;
}

这个类中还有一个属性m_pReleasePoolStack 属于CCArray类型,表示对象自动释放池数组,其中可以有多个对象自动释放池。


比如说,在上面的 getCurReleasePool() 方法中,如果 m_pCurReleasePool 为空,那么就要进入push()方法创建一个对象自动释放池,并且添加到m_pReleasePoolStack中。

void CCPoolManager::push()
{
    CCAutoreleasePool* pPool = new CCAutoreleasePool();       //ref = 1
    m_pCurReleasePool = pPool;

    m_pReleasePoolStack->addObject(pPool);                   //ref = 2

    pPool->release();                                       //ref = 1
}

如代码所示,push()操作 new 了一个自动释放池对象,并且将它赋值给 当前自动释放池(m_pCurReleasePool),然后将这个new的自动释放池对象放到CCPoolManager 里面的自动释放池数组中。


注意过程中对其引用计数的控制,自动释放池本身也是继承ccobject的,也有自己的引用计数,受相同的内存管理方式。其中的注释就表明了自动释放池对象的引用计数。


好,大致已经介绍了CCPoolManager 和 CCAutoreleasePool 这两个类,那么再来看将对象自动管理autorelease的过程就比较简单了。


首先调用构造方法,new一个对象(我们称之为object),然后这个对象调用autorelease方法,接着就是得到

CCPoolManager的单例变量,用getCurReleasePool() 方法获取到当前的对象自动释放池对象(如果没有的话,就新建一个对象自动释放池对象,并且添加到 对象自动释放池数组中),然后把这个object对象添加到自动释放池中的

m_pManagedObjectArray 数组中,执行完这个过程,这个新建的object对象的引用计数值为1,被管理状态值也为1(表征该对象是自动管理的)


下面通过一个简单的例子来说明上面所讲述的内容:

1、new创建一个对象,还没有autorelease。


Cocos2d-x 内存管理剖析(1)_第1张图片

2、对对象进行autorelease操作



Cocos2d-x 内存管理剖析(1)_第2张图片


3、对对象进行retain操作:

Cocos2d-x 内存管理剖析(1)_第3张图片


Cocos2d-x 内存管理剖析(1)_第4张图片






你可能感兴趣的:(内存管理,cocos2d-x)