Cocos2d-x从入门到精通第七课《内存管理》

前端之家收集整理的这篇文章主要介绍了Cocos2d-x从入门到精通第七课《内存管理》前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
课程视频地址: http://edu.csdn.net/course/detail/1342/20992?auto_start=1

一. 什么是引用计数



引用计数是现代内存管理中经常使用到的一个概念,它的基本思想是通过计数方式实现多个不同对象同时引用一个共享对象,具体地讲,当创建一个对象的实例并在堆上分配内存时,对象的引用计数为1,在其他对象中需要持有这个共享对象时,需要把共享对象的引用计数加1,当其他对象不再持有该共享对象时,共享对象的引用计数减1,当共享对象的引用计数变成0时,对象的内存会被立即释放。

在Cocos2d-x中,则定义了retain,release和autorelease函数,分别用于增加计数、减少计数以及将一个对象交给自动释放池对象AutoreleasePool进行管理,由AutoreleasePool对象负责调用release函数


二. Ref类



Ref类实现了引用计数的功能,它是引擎代码中绝大多数其他类的父类,定义在CCRef.h中,实现在CCRef.cpp中。其实在CCRef.h文件中不止定义了Ref类,还定义了Clonable类、一系列的宏定义和类型定义,不过我们暂且将精力放在Ref类的解读上。Ref类使用私有成员变量_referenceCount保存计数值,并通过retain,release和autorelease函数实现增减计数值。下面我们来看一下Ref类的代码,从源码进行分析,代码如下:

#ifndef __BASE_CCREF_H__
#define __BASE_CCREF_H__


#include "platform/CCPlatformMacros.h"
#include "base/ccConfig.h"
//内存泄露检测的开关
#define CC_REF_LEAK_DETECTION 0


NS_CC_BEGIN


class Ref;


/** Interface that defines how to clone an Ref */
//Clonable类定义了复制Ref类对象的规约,3.0以上版本建议使用clone函数,copy函数已属于废弃函数。
class CC_DLL Clonable
{
public:
    /** returns a copy of the Ref */
    //返回一个copy的Ref对象,纯虚函数,需要重写
    virtual Clonable* clone() const = 0;
    /**
     * @js NA
     * @lua NA
     */
    virtual ~Clonable() {};


    /** returns a copy of the Ref.
     * @deprecated Use clone() instead
     */
    //被废弃
    CC_DEPRECATED_ATTRIBUTE Ref* copy() const
    {
        // use "clone" instead
        CC_ASSERT(false);
        return nullptr;
    }
};


class CC_DLL Ref
{
public:
    
    //保留,引用计数+1
    void retain();


    //释放,引用计数-1
    void release();


    Ref* autorelease();


    //获得引用计数
    unsigned int getReferenceCount() const;


protected:
    
    Ref();


public:
    
    //析构
    virtual ~Ref();


protected:
    //引用的计数,retain 会 +1  release会 -1
    unsigned int _referenceCount;
    //设定CAutoreleasePool为友元类,这是一个通过CCObject指针容器CCMutableArray来对CCObject实例对象的内存进行管理的类,CCMutableArray在加入CCObject时对其引用计数器加1,在移除CCObject时对其引用计数器减1。
    friend class AutoreleasePool;


#if CC_ENABLE_SCRIPT_BINDING
public:
    // 唯一ID
    unsigned int        _ID;
    /// Lua reference id
    // 在LUA脚本引擎中的访问标识ID.暂可不理会,待学习到LUA时再分析
    int                 _luaID;
    //swift脚本的对象指针,为支持swift脚本所定义的
    void* _scriptObject;
#endif


#if CC_USE_MEM_LEAK_DETECTION
public:
    //打印内存泄露的信息
    static void printLeaks();
#endif
};


class Node;
//动作类的回调函数
typedef void (Ref::*SEL_CallFunc)();
typedef void (Ref::*SEL_CallFuncN)(Node*);
typedef void (Ref::*SEL_CallFuncND)(Node*,void*);
typedef void (Ref::*SEL_CallFuncO)(Ref*);
//menuitem的回调函数
typedef void (Ref::*SEL_MenuHandler)(Ref*);
//schedule的回调函数
typedef void (Ref::*SEL_SCHEDULE)(float);


#define CC_CALLFUNC_SELECTOR(_SELECTOR) static_cast<cocos2d::SEL_CallFunc>(&_SELECTOR)
#define CC_CALLFUNCN_SELECTOR(_SELECTOR) static_cast<cocos2d::SEL_CallFuncN>(&_SELECTOR)
#define CC_CALLFUNCND_SELECTOR(_SELECTOR) static_cast<cocos2d::SEL_CallFuncND>(&_SELECTOR)
#define CC_CALLFUNCO_SELECTOR(_SELECTOR) static_cast<cocos2d::SEL_CallFuncO>(&_SELECTOR)
#define CC_MENU_SELECTOR(_SELECTOR) static_cast<cocos2d::SEL_MenuHandler>(&_SELECTOR)
#define CC_SCHEDULE_SELECTOR(_SELECTOR) static_cast<cocos2d::SEL_SCHEDULE>(&_SELECTOR)


// Deprecated
#define callfunc_selector(_SELECTOR) CC_CALLFUNC_SELECTOR(_SELECTOR)
#define callfuncN_selector(_SELECTOR) CC_CALLFUNCN_SELECTOR(_SELECTOR)
#define callfuncND_selector(_SELECTOR) CC_CALLFUNCND_SELECTOR(_SELECTOR)
#define callfuncO_selector(_SELECTOR) CC_CALLFUNCO_SELECTOR(_SELECTOR)
#define menu_selector(_SELECTOR) CC_MENU_SELECTOR(_SELECTOR)
#define schedule_selector(_SELECTOR) CC_SCHEDULE_SELECTOR(_SELECTOR)




// end of base_nodes group
/// @}


NS_CC_END


#endif // __BASE_CCREF_H__

Ref将构造函数声明为保护类型,防止直接生成Ref对象,在构造函数的成员初始化列表中将引用计数值_referenceCount初始化为1。retain函数将_referenceCount加1,release函数则减1,autorelease函数则将对象托管给AutoreleasePool对象进行管理,具体实现代码如下:


#include "base/CCRef.h"
#include "base/CCAutoreleasePool.h"
#include "base/ccMacros.h"
#include "base/CCScriptSupport.h"


#if CC_REF_LEAK_DETECTION
#include <algorithm>    // std::find
#endif


NS_CC_BEGIN


#if CC_REF_LEAK_DETECTION
static void trackRef(Ref* ref);
static void untrackRef(Ref* ref);
#endif


Ref::Ref()
: _referenceCount(1) // when the Ref is created,the reference count of it is 1
{
#if CC_ENABLE_SCRIPT_BINDING
    //定义一个静态UINT类型变量做为实例对象计数器,此值只会增长,不会减少,保证唯一。
    static unsigned int uObjectCount = 0;
    //脚本对象的Id
    _luaID = 0;
    //注意:所有由此CCObject类派生的子类也会拥有这个唯一的ID。它可以使我们通过唯一ID来获取相应的实例对象。
    _ID = ++uObjectCount;
    _scriptObject = nullptr;
#endif
    
#if CC_REF_LEAK_DETECTION
    //加入到保存Ref的list中
    trackRef(this);
#endif
}


Ref::~Ref()
{
#if CC_ENABLE_SCRIPT_BINDING
    // if the object is referenced by Lua engine,remove it
    //如果这个对象被lua脚本引擎引用了,就删除掉
    if (_luaID)
    {
        ScriptEngineManager::getInstance()->getScriptEngine()->removeScriptObjectByObject(this);
    }
    else
    {
        //获得脚本引擎
        ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
        //如果这个脚本引擎是javascript
        if (pEngine != nullptr && pEngine->getScriptType() == kScriptTypeJavascript)
        {
            //删除对象
            pEngine->removeScriptObjectByObject(this);
        }
    }
#endif




#if CC_REF_LEAK_DETECTION
    //从保存Ref的list中删除
    if (_referenceCount != 0)
        untrackRef(this);
#endif
}


void Ref::retain()
{
    CCASSERT(_referenceCount > 0,"reference count should greater than 0");
    //引用计数+1
    ++_referenceCount;
}


void Ref::release()
{
    CCASSERT(_referenceCount > 0,"reference count should greater than 0");
    //引用计数-1
    --_referenceCount;


    if (_referenceCount == 0)
    {
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
        auto poolManager = PoolManager::getInstance();
        if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
        {
            
            CCASSERT(false,"The reference shouldn't be 0 because it is still in autorelease pool.");
        }
#endif


#if CC_REF_LEAK_DETECTION
        //从保存Ref*的list中删除
        untrackRef(this);
#endif
        delete this;
    }
}


Ref* Ref::autorelease()
{
    //调用内存管理器实例对象的addObject函数加入当前Ref实例对象的指针
    PoolManager::getInstance()->getCurrentPool()->addObject(this);
    return this;
}


unsigned int Ref::getReferenceCount() const
{
    //返回该对象的引用计数
    return _referenceCount;
}


#if CC_REF_LEAK_DETECTION
//创建保存Ref对象的list
static std::list<Ref*> __refAllocationList;


void Ref::printLeaks()
{
    // Dump Ref object memory leaks
    //如果是空的不做处理
    if (__refAllocationList.empty())
    {
        log("[memory] All Ref objects successfully cleaned up (no leaks detected).\n");
    }
    else    //不为空
    {
        //打印__refAllocationList的长度
        log("[memory] WARNING: %d Ref objects still active in memory.\n",(int)__refAllocationList.size());
        //循环打印Ref对象的信息
        for (const auto& ref : __refAllocationList)
        {
            CC_ASSERT(ref);
            const char* type = typeid(*ref).name();
            log("[memory] LEAK: Ref object '%s' still active with reference count %d.\n",(type ? type : ""),ref->getReferenceCount());
        }
    }
}


static void trackRef(Ref* ref)
{
    CCASSERT(ref,"Invalid parameter,ref should not be null!");


    // Create memory allocation record.
    //把ref对象添加到list中
    __refAllocationList.push_back(ref);
}


static void untrackRef(Ref* ref)
{
    //获得ref的迭代器
    auto iter = std::find(__refAllocationList.begin(),__refAllocationList.end(),ref);
    //如果没找到
    if (iter == __refAllocationList.end())
    {
        log("[memory] CORRUPTION: Attempting to free (%s) with invalid ref tracking record.\n",typeid(*ref).name());
        return;
    }
    //从list中释放ref
    __refAllocationList.erase(iter);
}


#endif // #if CC_USE_MEM_LEAK_DETECTION




NS_CC_END




我们看到Ref其实真的很单纯,它主要就是有两个功能,一个是提供引用计数增减的方法。一个是通过引用计数交给内存管理器进行内存管理。下面我们来重点看一下autorelease函数的意义,顾名思义,“自动释放”。也就是说调用函数则当前CCObject实例对象不需要用户在外部去手动调用release进行内存的释放工作。我们已经知道它通过引用计数来处理在什么时候内存释放。Cocos2d-x是怎么做到的呢?

在autorelease函数中有这么一句

//调用内存管理器实例对象的addObject函数加入当前Ref实例对象的指针

PoolManager::getInstance()->getCurrentPool()->addObject(this);

PoolManager代表了内存管理器。此句调用PoolManager的实例对象获得AutoreleasePool对象,然后调用AutoreleasePool的addObject函数将当前Ref对象的指针交给内存管理器。下面我们就来看看这辆类的实现。


三. PoolManager和AutoreleasePool



内存管理器主要用来管理Cocos2d-x内存自动释放的,在Cocos2d-x的内存管理中起到了重要的作用,下面我们通过AutorelesePool和PoolManager两个类的代码来分析一下Ccocos2d-x内存管理的原理。首先我们来看一下CCAutoreleasePool.h:


#ifndef __AUTORELEASEPOOL_H__
#define __AUTORELEASEPOOL_H__


#include <vector>
#include <string>
#include "base/CCRef.h"


NS_CC_BEGIN


class CC_DLL AutoreleasePool
{
public:
    //构造
    AutoreleasePool();
    //通过name进行构造
    AutoreleasePool(const std::string &name);
    
    /**
     * @js NA
     * @lua NA
     */
    //析构
    ~AutoreleasePool();
    //添加Ref
    void addObject(Ref *object);


    //清除
    void clear();
    
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    //判断是否执行了clear()操作
    bool isClearing() const { return _isClearing; };
#endif
    
    //检查是否有object对象
    bool contains(Ref* object) const;
    //打印删除的对象的地址
    void dump();
    
private:
    //ref对象保存
    std::vector<Ref*> _managedObjectArray;
    //autoreleasepool的名字
    std::string _name;
    
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
   
    //是否执行清除操作的
    bool _isClearing;
#endif
};


class CC_DLL PoolManager
{
public:
  
    CC_DEPRECATED_ATTRIBUTE static PoolManager* sharedPoolManager() { return getInstance(); }
    //获得poolManager的实例
    static PoolManager* getInstance();
    
    
    CC_DEPRECATED_ATTRIBUTE static void purgePoolManager() { destroyInstance(); }
    //销毁
    static void destroyInstance();
    
    
    //获得当前的autoreleasepooll
    AutoreleasePool *getCurrentPool() const;
    //判断obj是否在autoreleasepool
    bool isObjectInPools(Ref* obj) const;


    /**
     * @js NA
     * @lua NA
     */
    //把AutoreleasePool设置成友元类
    friend class AutoreleasePool;
    
private:
    //构造
    PoolManager();
    //析构
    ~PoolManager();
    //加入
    void push(AutoreleasePool *pool);
    //弹出
    void pop();
    //poolmanager的实例
    static PoolManager* s_singleInstance;
    //保存AutoreleasePool对象的vector
    std::vector<AutoreleasePool*> _releasePoolStack;
};


// end of base_nodes group
/// @}


NS_CC_END


#endif //__AUTORELEASEPOOL_H__



从CCAutoreleasePool.h中可以看到声明了两个类,分别是AutoreleasePool,PoolManager,AutoreleasePool主要用来管理Ref对象,而PoolManager主要用来管理AutoreleasePool,具体这两个类是怎么实现的呢,我们来看看CCAutoreleasePool.cpp的代码

#include "base/CCAutoreleasePool.h"
#include "base/ccMacros.h"


NS_CC_BEGIN


AutoreleasePool::AutoreleasePool()
: _name("") //设置名字为""
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0),_isClearing(false) //设置_isClearing为false
#endif
{
    //申请vector的size增加150
    _managedObjectArray.reserve(150);
    //把该autoreleasePool添加到管类类
    PoolManager::getInstance()->push(this);
}


AutoreleasePool::AutoreleasePool(const std::string &name)
: _name(name)   //设置名称
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0),_isClearing(false)    //设置_isClearing
#endif
{
    //申请vector的size增加150
    _managedObjectArray.reserve(150);
    //把该autoreleasePool添加到管类类
    PoolManager::getInstance()->push(this);
}


AutoreleasePool::~AutoreleasePool()
{
    CCLOGINFO("deallocing AutoreleasePool: %p",this);
    //清除
    clear();
    //弹出
    PoolManager::getInstance()->pop();
}


void AutoreleasePool::addObject(Ref* object)
{
    //把object添加到_managedObjectArray
    _managedObjectArray.push_back(object);
}


void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = true; //设置为执行了清空操作
#endif
    //遍历_managedObjectArray所有的Ref
    for (const auto &obj : _managedObjectArray)
    {
        //调用obj的release,对obj的引用计数-1
        obj->release();
    }
    //清空_managedObjectArray
    _managedObjectArray.clear();
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = false;  //设置为未执行了清空操作
#endif
}


bool AutoreleasePool::contains(Ref* object) const
{
    //遍历_managedObjectArray
    for (const auto& obj : _managedObjectArray)
    {
        //如果相等
        if (obj == object)
            //返回true,表示_managedObjectArray中有该对象
            return true;
    }
    return false;
}


void AutoreleasePool::dump()
{
    //打印_managedObjectArray的size,用于调试
    CCLOG("autorelease pool: %s,number of managed object %d\n",_name.c_str(),static_cast<int>(_managedObjectArray.size()));
    CCLOG("%20s%20s%20s","Object pointer","Object id","reference count");
    for (const auto &obj : _managedObjectArray)
    {
        CC_UNUSED_PARAM(obj);
        //打印对象的引用计数
        CCLOG("%20p%20u\n",obj,obj->getReferenceCount());
    }
}




//--------------------------------------------------------------------
//
// PoolManager
//
//--------------------------------------------------------------------
//设置PoolManager的单例对象为nullptr
PoolManager* PoolManager::s_singleInstance = nullptr;


PoolManager* PoolManager::getInstance()
{
    //判断是否存在s_singleInstance
    if (s_singleInstance == nullptr)
    {
        //如果s_singleInstance为nullptr,new一个PoolManager
        s_singleInstance = new (std::nothrow) PoolManager();
        // Add the first auto release pool
        //new一个AutoreleasePoll并取名字为cocos2d autorelease pool
        new AutoreleasePool("cocos2d autorelease pool");
    }
    return s_singleInstance;
}


void PoolManager::destroyInstance()
{
    //删除s_singleInstance
    delete s_singleInstance;
    s_singleInstance = nullptr;
}


PoolManager::PoolManager()
{
    //增加_releasePoolStack容器的size
    _releasePoolStack.reserve(10);
}


PoolManager::~PoolManager()
{
    CCLOGINFO("deallocing PoolManager: %p",this);
    
    while (!_releasePoolStack.empty())
    {
        //获得最后一个AutoreleasePool
        AutoreleasePool* pool = _releasePoolStack.back();
        //删除
        delete pool;
    }
}




AutoreleasePool* PoolManager::getCurrentPool() const
{
    //获得当前的AutoreleasePool
    return _releasePoolStack.back();
}


bool PoolManager::isObjectInPools(Ref* obj) const
{
    //遍历所有的AutoreleasePool
    for (const auto& pool : _releasePoolStack)
    {
        //如果pool中存在obj,返回true
        if (pool->contains(obj))
            return true;
    }
    return false;
}


void PoolManager::push(AutoreleasePool *pool)
{
    //把AutoreleasePool添加到_releasePoolStack
    _releasePoolStack.push_back(pool);
}


void PoolManager::pop()
{
    CC_ASSERT(!_releasePoolStack.empty());
    //删除当前的AutoreleasePool
    _releasePoolStack.pop_back();
}


NS_CC_END



看完上面的代码,大家是不是有点晕啊,下面我来总结一下,首先Autorelease提供了_managedObjectArray的vector容器来保存Ref对象,通过对_managedObjectArray的添加删除来实现Ref的添加删除
通过PoolManager来管理AutoreleasePool,通过PoolManager的pop,push来完成对AutoreleasePool增减,通过clear来实现AutoReleasePool的成员Ref的删除
看完解释你可能还有一个疑问,说了这么多,那么Cocos2d-x是如果通过调用PoolMananger的相关方法来管理内存的呢,带着这个疑问我们来看看下一个知识点。


四.Cocos2d-x内存管理的运行原理



还记得我们之前讲的渲染流程吗,从Application类的run()方法开始,一直完成了整个渲染流程的介绍,那么内存管理的入口在哪儿了,聪明的你可能已经猜到了,没错也是在Application类的run()方法,下面我们通过Cocos2d-x的源码来看一下如何调用到PoolManager的,首先打开Application的run()方法,在run()方法中我们可以看到如下代码
director->mainLoop();
代码调用了mainLoop()方法,该方法就是Cocos2d-x的住循环的方法,每帧都会被调用一次。进入该方法,我们可以看到如下代码
PoolManager::getInstance()->getCurrentPool()->clear();
在mainLoop中调用了PoolManager获得当前的AutoreleasePool的clear()方法调用存放在AutoreleasePool中的所有对象release()方法,在release()中首先对Ref对象的引用计数-1,如果引用计数为0那么就delete掉该对象。
看完上面说的一大堆东东,大家是不是已经有点晕了,下面我们通过一个实例来看看:
//创建一个Node,create方法会对node的引用计数+1,并在create方法调用了node的autorelease方法,把它加入到了自动释放池
Node* node = Node::create();
//把Node添加到Layer上,addChild操作会对node的引用计数+1
this->addChild(node);
通过代码注释我们可以得出,现在node的引用计数为2,接着随着在mainLoop的
PoolManager::getInstance()->getCurrentPool()->clear();
调用,我们对所有的Ref的引用计数进行了-1操作,当然我们的node对象也执行了改操作,那么我们的node对象的引用计数就变成了 1,接着对node进行了addchild的操作,在addchild中其实对要添加的对象进行了retain操作,所以addChil操作之后node的引用计数变成了2.然后随着在mainloop中调用 AutoreleasePool的clear()方法之后,node的引用计数就减一了,同时把保存该node的_managedObjectArray进行了clear()操作,也是说把node从AutoreleasePool的managedObjectArray中进行了清除操作,所以在mainloop中在下一次执行AutoreleasePool的clear()的时候不会再次对node进行release操作,所以node的引用计数会一直为1,只用当调用node的remove()方法的时候,才会对node的进行release操作,release操作首先对其引用计数-1,那么node的引用计数就变成了0,然后就会delete掉改node对象,也就完成了node的自动释放释放。


可能大家会感觉有点绕,但是通过配合源码来仔细研读这段话,相信让你对Cocos2d-x的内存管理有个新的认识的。


五.Cocos2d-x内存管理总结



本节课主要讲解了Cocos2d-x内存管理,我们通过源码的解析了解了Cocos2d-x内存管理的原理,并通过node的创建的例子来学习了内存管理。Cocos2d-x内存管理是一个比较难的概念,在平时工作中只有理解了Cocos2d-x的内存管理的原理,才能更好的使用它,因为我们的每个Cocos2d-x的内置类都是通过它来进行管理的。


六.作业:

仔细研读Ref,AutoreleasePool,PoolManager三个类的源码。 原文链接:https://www.f2er.com/cocos2dx/341536.html

猜你在找的Cocos2d-x相关文章