【Cocos2d-x源码分析】 Cocos2d-x内存管理解析

原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/50317683

在使用Cocos2dx时,当我们需要某个对象实例,只要简单地调用相应静态create方法就可以得到该对象实例,使用完之后我们也无需手动地释放该对象内存而没有产生内存泄露问题。你知道其中的原理是什么么?今天我带着这个疑问,通过阅读Cocos2d-x的源码来探讨一下其内存管理的方法。下面的内容都是在Cocos2dx-3.6的基础上讲解的。

首先我们从一个程序的启动开始,一个Cocos2dx程序的程序入口再main.cpp文件中。

代码1:

int main(int argc, char *argv[])
{
    AppDelegate app;
    return Application::getInstance()->run();
}

在代码1中,程序启动时只做了两件事:一是创建了AppDelegate对象,二是执行Application单例的run方法。我们可以看看AppDelegate类的定义,这只是一个委托类,用来处理程序生命周期中启动、暂停等事件回调。我们进一步跟踪到run()函数内部,代码如下:

代码2:

int Application::run()
{
    initGLContextAttrs();
    if(!applicationDidFinishLaunching())
    {
        return 1;
    }

    long lastTime = 0L;
    long curTime = 0L;

    auto director = Director::getInstance();
    auto glview = director->getOpenGLView();

    // Retain glview to avoid glview being released in the while loop
    glview->retain();

    while (!glview->windowShouldClose())
    {
        lastTime = getCurrentMillSecond();

        director->mainLoop();
        glview->pollEvents();

        curTime = getCurrentMillSecond();
        if (curTime - lastTime < _animationInterval)
        {
            usleep(static_cast<useconds_t>((_animationInterval - curTime + lastTime)*1000));
        }
    }

    /* Only work on Desktop
    *  Director::mainLoop is really one frame logic
    *  when we want to close the window, we should call Director::end();
    *  then call Director::mainLoop to do release of internal resources
    */
    if (glview->isOpenGLReady())
    {
        director->end();
        director->mainLoop();
    }

    glview->release();

    return 0;
}

在代码2中,先是执行了部分初始化工作,然后进入了游戏的主循环。到这里,我们也没有看到有关内存管理的代码,这时,我们不妨大胆猜测一下,游戏的执行说到底就是根据玩家输入(触屏)计算,然后不断刷新屏幕的过程,那它的内存管理是不是也在游戏的主循环中呢?我们进入mainLoop()函数,代码如下:
代码3:

void DisplayLinkDirector::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();
    }
    else if (_restartDirectorInNextLoop)
    {
        _restartDirectorInNextLoop = false;
        restartDirector();
    }
    else if (! _invalid)
    {
        drawScene();

        // release the objects
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

在这段代码里,我们看到Cocos2dx的注释:release the objects。这不正是释放内存的功能么!先不要激动,我们仔细看看这段代码。在主循环中,每一次循环引擎先绘制当前场景,然后调用clear()函数释放已经“死亡”的对象。这时又遇到了一个新的类PoolManager,这个类又是干什么用得呢?我们暂时不理,继续跟进到clear()函数内部。代码如下:

代码4:

void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = true;
#endif
    std::vector<Ref*> releasings;
    releasings.swap(_managedObjectArray);
    for (const auto &obj : releasings)
    {
        obj->release();
    }
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = false;
#endif
}

在代码4中,clear()函数定义在AutoreleasePool中,这段代码通过遍历一个vector,其中每个元素都是Ref类型,调用该对象的release()函数。这是我们的疑问越来越大了:前面有一个PoolManager类,现在又遇到Ref类型对象和AutoreleasePool类,这些类有什么联系吗?别急,我们先来看看release()函数。代码如下:

代码5:

void Ref::release()
{
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
    --_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
        untrackRef(this);
#endif
        delete this;
    }

代码5中,Ref对象调用release()方法把自身的_referenceCount减1,如果该值减为0后就删除该对象。看到这里,我们是不是应该有点头绪了,referenceCount不就是引用计数器么?“引用计数”不是内存管理的方法之一么?难道Cocos2dx是采用“引用计数”法来管理内存的?带着疑问,我们把Ref类的源码看完。

代码6:

class CC_DLL Ref
{
public:
    /**
     * This increases the Ref's reference count.
     * 引用计数加1
     */
    void retain();

    /**
     * This decrements the Ref's reference count.
     * 引用计数减1
     *
     * If the reference count reaches 0 after the descrement, this Ref is
     * destructed.
     * 如果引用计数为0,则销毁该对象
     */
    void release();

    /**
     * Releases the ownership sometime soon automatically.
     * 自动释放引用计数,返回对象本身
     */
    Ref* autorelease();

    /**
     * Returns the Ref's current reference count.
     * 返回引用计数值
     */
    unsigned int getReferenceCount() const;

protected:
    /** 
     * 构造器
     */
    Ref();

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

protected:
    /// count of references 引用计数变量
    unsigned int _referenceCount;

    friend class AutoreleasePool;

};

为了便于理解,我把无关代码删除了,从Ref的头文件说明中,我们可以看到一个用于计数的的整形变量_referenceCount,它记录了当前Ref对象的引用次数。接下来,我们看看Ref是怎样实现引用计数的。

代码7:

Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
{
#if CC_ENABLE_SCRIPT_BINDING
    static unsigned int uObjectCount = 0;
    _luaID = 0;
    _ID = ++uObjectCount;
    _scriptObject = nullptr;
#endif

#if CC_REF_LEAK_DETECTION
    trackRef(this);
#endif
}

Ref的构造函数中有一个初始化列表,表示_referenceCount也就是引用计数变量初始化为1。另外细心地童鞋还可能会注意到Ref类的构造器是被设置为protected访问权限的,也就是说Ref不能直接实例化,只能由其子类来实例化。
至于Ref的release()和retain()函数都比较简单,代码如下:

代码8:

void Ref::retain()
{
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
    ++_referenceCount;
}

void Ref::release()
{
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
    --_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
        untrackRef(this);
#endif
        delete this;
    }
}

在代码8中,retain()就是将引用计数加1,而release()函数先减少一次该对象的引用计数。当引用计数减为0时,则销毁该对象。
而autorelease()函数就有点“复杂”了,代码如下:

代码9:

Ref* Ref::autorelease()
{
    PoolManager::getInstance()->getCurrentPool()->addObject(this);
    return this;
}

在这里,又出现了PoolManager单例对象,autorelease()函数将当前Ref对象加入到一个pool池中,这个pool又是什么呢?在Ref的头文件说明中,有一个friend类AutoreleasePool类,是不是要将这个对象放入这个pool中呢?这个我们稍后再解释。分析到这里我们先来总结一下。
从上面的分析中,我们可以初步知道

  • Cocos2dx是通过“引用计数”
  • 方法来管理内存的 “引用计数”法的关键在于Ref类。该类的retain函数会将当前对象的引用计数加1,该类的release()函数会将当前对象的应用计数减1。另外,Ref对象创建后其引用计数值为1.

接下来,我们继续分析。我们前面遇到过PoolManager类和AutoreleasePool类,从命名上看,PoolManager很可能是AutoreleasePool的管理类,所以我们先看看AutoreleasePool的源码:

代码10:

class CC_DLL AutoreleasePool
{
public:

    AutoreleasePool();

    /**
     * Create an autorelease pool with specific name. This name is useful for debugging.
     * 创建一个自动释放池,并给该释放池定义一个名字
     */
    AutoreleasePool(const std::string &name);

    ~AutoreleasePool();

    /**
     * Add a given object to this autorelease pool.
     * 将一个对象添加到自动释放池
     */
    void addObject(Ref *object);

    /**
     * Clear the autorelease pool.
     * 清空当前释放池
     */
    void clear();

#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    /**
     * Whether the autorelease pool is doing `clear` operation.
     * 返回_isClearing,标识自动释放池是否在执行“清理”操作
     */
    bool isClearing() const { return _isClearing; };
#endif

    /**
     * Checks whether the autorelease pool contains the specified object.
     * 检查自动释放池中是否包含指定对象
     */
    bool contains(Ref* object) const;

    /**
     * Dump the objects that are put into the autorelease pool. It is used for debugging.
     * 打印调试信息
     */
    void dump();

private:
    /**
     * The underlying array of object managed by the pool.
     * 对象管理列表,存放着加入该释放池的所有对象
     */
    std::vector<Ref*> _managedObjectArray;
    std::string _name;

#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    /**
     *  The flag for checking whether the pool is doing `clear` operation.
     */
    bool _isClearing;
#endif
};

从源代码中得注释中可以看到,AutoreleasePool类描述了一个自动释放对象池结构。该对象中最主要的成员变量是_managedObjectArray,它是一个vector,保存了所有加入到自动释放池中得对象。

接下来,我们看看AutoreleasePool类的具体实现:

代码11:

AutoreleasePool::AutoreleasePool()
: _name("")
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
, _isClearing(false)
#endif
{
    _managedObjectArray.reserve(150);
    PoolManager::getInstance()->push(this);
}

AutoreleasePool::AutoreleasePool(const std::string &name)
: _name(name)
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
, _isClearing(false)
#endif
{
    _managedObjectArray.reserve(150);
    PoolManager::getInstance()->push(this);
}

代码11显示的是AutoreleasePool的两个构造函数,他们都只做了两件事情:一是将保存自动释放对象的_managedObjectArray的容量扩大为150(reserve是vector的成员函数,设置了vector得最小容量);二是将该自动释放池纳入PoolManager的管理中。这里我们又一次遇到PoolManager单例对象。

代码12:

void AutoreleasePool::addObject(Ref* object)
{
    _managedObjectArray.push_back(object);
}

代码12中,将一个Ref对象加入到自动释放池中。到这里,我们再回头看看Ref类中得autorelease()函数,通过调用对象的autorelease()函数,会将对象加入到当前的自动释放池中去统一进行管理,并在某一时间进行释放。

再看看AutoreleasePool类中的clear()函数。代码如下:

代码13:

void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = true;
#endif
    std::vector<Ref*> releasings;
    releasings.swap(_managedObjectArray);
    for (const auto &obj : releasings)
    {
        obj->release();
    }
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = false;
#endif
}

clear()函数遍历了自动释放池中得所有对象,并对每个元素调用其release()函数。前面我们已经分析过,Ref对象调用release()函数后,会将自身的引用计数值减去1,但是不一定就意味着销毁该对象,该对象是否被销毁取决于其引用计数值是否为0。

AutoReleasPool还有其它函数,都比较简单,这里就不一一分析。

接下来,我们来看看前面数次碰到的PoolManager类,代码如下:

代码14:

class CC_DLL PoolManager
{
public:
    // 获取PoolManager的单例对象
    static PoolManager* getInstance();

    // 销毁PoolManager单例对象
    static void destroyInstance();

    /**
     * Get current auto release pool, there is at least one auto release pool that created by engine.
     * You can create your own auto release pool at demand, which will be put into auto releae pool stack.
     * 获取当前使用的自动释放池
     */
    AutoreleasePool *getCurrentPool() const;

    bool isObjectInPools(Ref* obj) const;

    friend class AutoreleasePool;

private:
    PoolManager();
    ~PoolManager();

    /**
     * 将一个自动释放池加入到PoolManager中
     */
    void push(AutoreleasePool *pool);
    /**
     * 弹出当前使用的自动释放池
     */
    void pop();

    static PoolManager* s_singleInstance;

    // 利用一个vector保存所有的自动释放对象池
    std::vector<AutoreleasePool*> _releasePoolStack;
};

故名思议,PoolManager是对所有AutoreleasePool进行管理的管理器,它是一个单例对象。它PoolManager的成员变量中存在一个vector类型的_releasePoolStack变量,用来保存所有的AutoreleasePool对象。PoolManager部分函数的具体实现如下:

代码15:

PoolManager* PoolManager::getInstance()
{
    if (s_singleInstance == nullptr)
    {
        s_singleInstance = new (std::nothrow) PoolManager();
        // Add the first auto release pool
        new AutoreleasePool("cocos2d autorelease pool");
    }
    return s_singleInstance;
}

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

PoolManager::PoolManager()
{
    _releasePoolStack.reserve(10);
}

PoolManager的构造函数把_releasePoolStack的最小容量设置为15。然后在获取单例函数getInstance()函数中创建了一个引擎默认的自动释放池。不知道大家会不会奇怪:为什么只有new AutoreleasePool(“cocos2d autorelease pool”)而没有把新创建的自动释放子放入_releasePoolStack容器中?原因在于AutoreleasePool的构造器中已经执行了push操作,如下:

PoolManager::getInstance()->push(this);

这样当引擎启动后就会由一个默认的自动释放池被创建并加入到PoolManager得栈底(实际上是vector)。

到现在为止,我们已经查看了与内存管理相关的Ref、AutoreleasePool和PoolManager的源码,分析了它们的联系。但是我们要知道,创建一个对象后是拿来用得,如果仅仅是简单地创建一个对象(把它的引用计数值设为1)而没有使用,那么它就在下一帧马上就会被销毁,这样是没有意义的。在使用这些对象时,何时需要执行retain()操作,何时需要执行release()操作呢?这个引擎也为我们考虑好了。具体来说:在创建出一个对象后,如果调用addChild()方法把该对象添加到父节点时,会调用该对象的retain()方法;如果通过removeChild()将对象从父节点中移除时,会调用该对象的release()方法将其引用计数值减1。具体实现可以跟踪Cocos2dx的源码了解。


至此,我们就搞清楚了原来Cocos2dx使用“引用计数”和“自动释放池”的方式来管理内存,以减轻开发人员的负担。这其中涉及Ref、AutoreleasePool和PoolManager三个类。进入游戏的主循环后,引擎绘制完每一帧都会清理一次内存池,释放引用计数值为0的对象。这就是Cocos2dx的内存管理机制。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: cocos2d-x是一款流行的开源游戏引擎,它可以用作开发本地移动游戏和桌面游戏。利用cocos2d-x游戏源码,开发人员可以快速构建流畅、高效、具有吸引力的游戏。该引擎使用C ++语言和脚本语言Lua来实现游戏开发,用户可以根据自己的需要进行选择。 cocos2d-x游戏源码包含许多强大的功能和工具,例如精灵、动画、场景管理、碰撞检测和音频控制等。通过这些功能,开发人员可以方便地创建各种类型的游戏,例如动作游戏、射击游戏、冒险游戏和益智游戏等。 此外,cocos2d-x游戏源码还具有高度定制化的特性,允许开发人员根据他们的需求自定义游戏元素。这种定制化可以通过改进游戏画面,添加新的模式和关卡,甚至整个新的游戏玩法来完成。 总之,cocos2d-x游戏源码是一款功能强大、易于使用和高度定制的游戏引擎,它可以帮助开发人员快速开发各种类型的游戏。无论你是专业开发人员还是无经验的游戏制作者,cocos2d-x都是一个极好的选择。 ### 回答2: Cocos2d-x游戏源码是使用Cocos2d-x引擎进行开发的游戏程序代码。Cocos2d-x引擎是一个开源的跨平台游戏引擎,可以支持iOS、Android、Windows Phone、Windows、MacOS和Linux等多个平台,并且提供了丰富的游戏开发API和工具集。Cocos2d-x游戏源码包含了游戏的核心逻辑、UI设计、动画效果、音频效果等各方面的代码,可以作为开发者学习和借鉴的重要资料。 由于Cocos2d-x引擎支持多种编程语言(如C++、Lua等),因此Cocos2d-x游戏源码也会有对应的编程语言版本。开发者可以通过阅读Cocos2d-x游戏源码,了解游戏开发过程中的技术细节,学习如何使用Cocos2d-x引擎进行游戏开发。同时,开发者也可以通过对Cocos2d-x游戏源码进行修改和优化,来满足游戏的特定需求,提升游戏的性能和用户体验。 总之,Cocos2d-x游戏源码是游戏开发者必备的重要资源,可以帮助开发者更好地了解游戏开发技术,提升游戏开发水平。 ### 回答3: cocos2d-x是一款优秀的游戏开发引擎,其源码包含了许多功能强大的游戏开发工具和API。使用cocos2d-x可以帮助开发者更快速更高效地开发出优秀的游戏作品。 cocos2d-x的游戏源码非常丰富,其包含了许多不同类型的游戏示例和模板,如平面射击、塔防、解谜等,这些示例可以帮助开发者更好地了解cocos2d-x的使用方法和原理。 cocos2d-x源码还包含了许多强大的API和工具,例如场景管理、动画控制、音频引擎等,这些工具和API能够帮助开发者更好地完成游戏的开发和调试。 此外,cocos2d-x源码还提供了完整的游戏开发框架,包括资源管理、内存管理、事件机制等,这些框架可以帮助开发者更好地组织代码和提高代码的可维护性。 总的来说,cocos2d-x游戏源码提供了丰富的工具和API,可以帮助开发者更高效地进行游戏开发,大大降低了开发难度和成本,是一款非常适合游戏开发者使用的引擎。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值