Cocos2d-X3.0 刨根问底四----- 内存管理源码分析


Cocos2d-X3.0 刨根问底(四)----- 内存管理源码分析

本系列文章发表以来得到了很多朋友的关注,小鱼在这里谢谢大家对我的支持,我会继续努力的,最近更新慢了一点,因为我老婆流产了抽了很多时间来照顾她希望大家谅解,并在此预祝我老婆早日康复。

上一篇,我们完整的分析了Director这个类,并提到了Director这个继承了Ref这个类,大致看了一下Ref这个类,是一个关于引用计数的类,从而我们可以推断Cocos2d-x用了一种引用计数的方式来管理内存对象,这一章我们刨根问底Cocos2d-x是如何实现内存管理及我们如何在实际项目开发中应用Cocos2d-x的内存管理。

打开CCRef.h文件,还好,这个文件并不大只有167行,整体浏览一遍,这个文件里一共定义了两个类 Clonable类和Ref类。

class CC_DLL Clonable
class CC_DLL Ref

Ref类是引用计数类,那么从命名上可以初步判断Clonable类应该是对象拷贝相关内容的类,不要跑题,我们是冲着Ref类来的先从这里下手,稍后再回过头来看Clonable是个什么球玩意。

Ref类的完整定义

class CC_DLL Ref
{
public:
    /**
     * 增加一次引用计数
     */
    void retain();
    
    /**
     * 释放一次计数,具体里面怎么释放的,下面我们跟定义的代码做分析
     */
    void release();

    /**
     * 自动释放
     */
    Ref* autorelease();

    /**
     * 得到当前对象的引用计数的值,也是是被引用了多少次
     */
    unsigned int getReferenceCount() const;
    
protected:
    /**
     * Constructor
     *
     * The Ref's reference count is 1 after construction.
     * @js NA
     */
    Ref();
    
public:
    /**
     * @js NA
     * @lua NA
     */
    virtual ~Ref();
    
protected:
    /// 用来记录引用次数的变量值
    unsigned int _referenceCount;
    
    friend class AutoreleasePool;
    
#if CC_ENABLE_SCRIPT_BINDING
public:
    /// 对象的ID
    unsigned int        _ID;
    /// 这个变量这里猜测是lua脚本中引用的ID
    int                 _luaID;
#endif
};

看了这个类的头文件定义,目前还只能了解了大概意思,Ref这个类用一个变量 _referenceCount来记录对象被引用了多少次,是否应该被释放,并且有一个增加引用 的方法 retain 和两个释放的方法 autorelease 和 release

还出现了一个新的类,为Ref的友元类 AutoreleasePool 从命名上断定这是一个 自动释放对象池,看来这个Ref类虽然代码量不大但并不简单。下面我们逐个分析这个类的几个方法,

在看实现源码时先说明一下,里面会有很多宏定义,这些宏定义都在

#include "CCPlatformMacros.h"
#include "ccConfig.h"

这两个文件里,碰到陌生的就查看一下这两个文件里的定义就可以了,由于Cocos2d-x的命名很规范,很多宏定义都可以通过命名来得知它的含义,这也就是前辈们的一句老话,最好的注释就是没有注释。

这里和大家分享一下读源码的经验,看别人的类的时候往往不要按着类定义的函数顺序去阅读,要根据逻辑思维的顺序去阅读。

这个Ref类我们从构造函数开始(大多数类都要从构造函数开始读)

再看一下 Ref的构造函数声明

protected:
    /**
     * Constructor
     *
     * The Ref's reference count is 1 after construction.
     * @js NA
     */
    Ref();

恩,没错,Ref的构造函数声明为 protected 的访问权限,那么这个Ref类是不可以被直接实例化的 只能有子类来实例化这个对象。(可能有些新手读者不理解什么叫实例化,可以简单理解实例化就是被 new现来的对象,不能实例化就是不能用new来创建对象)

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;
#endif
}

_referencecount 被初始化为 1 不难理解,当对象被创建的时候,肯定要用1次,所以这个引用计数必然在创建的时候为1

CC_ENABLE_SCRIPT_BINDING 支持脚本绑定,cocos2d-x 支持 js脚本和lua脚本。在 ccConfig.h中这个值CC_ENABLE_SCRIPT_BINDING 有定义为 1

这里定义了一个静态变量uObjectCount 这里用来记录创建的对象数量的,当Ref的子类创建对象的时候,基类的Ref的构造函数里 uobjectCount就会自增1.这也使所有对象都有唯一的_ID值不会重复.。从这块定义可以看到一个问题,这个函数并不是线程安全的,可以知道Cocos2d-x不适合多线程程序。

小结一下:构造函数里初始化了对象的 _ID 脚本 _luaID=0、并且初始化了引用计数 _referencecount为1。

看过了构造函数现在看析构函数

Ref::~Ref()
{
#if CC_ENABLE_SCRIPT_BINDING
    // if the object is referenced by Lua engine,remove it
    if (_luaID)
    {
        ScriptEngineManager::getInstance()->getScriptEngine()->removeScriptObjectByObject(this);
    }
    else
    {
        ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
        if (pEngine != NULL && pEngine->getScriptType() == kScriptTypeJavascript)
        {
            pEngine->removeScriptObjectByObject(this);
        }
    }
#endif
}

这个函数没什么太特别的,又新出来几个类ScriptEngineProtocol,ScriptEngineManager 从命名上理解,这两个类应该是脚本管理器

总结一下析构当这个对象被Lua脚本引用时,在销毁的时候通知脚本管理器把这个对象消除掉

当这个对象被其它脚本管理器引用是 这里尤其指 kScriptTypeJavascript 这个类型的脚本 js脚本。在这个对象销毁时通知 js管理器清除这个对象。

看代码切忌看到什么都跟进,那样就跳到深坑出不来了。这块我们就分析到这种程序,具体ScriptEngineProtocol,ScriptEngineManager 是什么玩意,分析脚本的时候我们再去看,这里只要知道在对象销毁的时候,会去通知脚本管理器消除对象就可以了。

到现在为止Ref的对象创建和销毁我们都了解了,下面看下引用 计数的操作。

先看 retain方法

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

retain的实现和想象中的没什么差别,就是增加了一次引用计数。

再看 release方法

void Ref::release()
{
    CCASSERT(_referenceCount > 0,"reference count should 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))
        {
            // Trigger an assert if the reference count is 0 but the Ref is still in autorelease pool.
            // This happens when 'autorelease/release' were not used in pairs with 'new/retain'.
            //
            // Wrong usage (1):
            //
            // auto obj = Node::create();   // Ref = 1,but it's an autorelease Ref which means it was in the autorelease pool.
            // obj->autorelease();   // Wrong: If you wish to invoke autorelease several times,you should retain `obj` first.
            //
            // Wrong usage (2):
            //
            // auto obj = Node::create();
            // obj->release();   // Wrong: obj is an autorelease Ref,it will be released when clearing current pool.
            //
            // Correct usage (1):
            //
            // auto obj = Node::create();
            //                     |-   new Node();     // `new` is the pair of the `autorelease` of next line
            //                     |-   autorelease();  // The pair of `new Node`.
            //
            // obj->retain();
            // obj->autorelease();  // This `autorelease` is the pair of `retain` of previous line.
            //
            // Correct usage (2):
            //
            // auto obj = Node::create();
            // obj->retain();
            // obj->release();   // This `release` is the pair of `retain` of previous line.
            CCASSERT(false,"The reference shouldn't be 0 because it is still in autorelease pool.");
        }
#endif
        delete this;
    }
}

代码这么一大段子,不要慌张,其实这个函数一点也不复杂,中间一大段的注释是告诉大家怎么样使用Cocos2d-x引用计数机制的。

分析代码,其实这个函数就干了两件事

  1. 当调用release方法时,先减少一次此对象的引用计数
  2. 当引用计数referenceCount值为0的时候表明其它地方不再需要这个对象了,那么就 delete this 销毁这个对象

中间那段含义是 在debug模式下如果引用计数已经为0并且这个对象还在自动释放池里面,报一个警告。

这里我们先不看那段说明引用机制用法的注释,那段注释最后再看。

release方法看过了,我们再看一下autorelease方法

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

autorelease方法更简单,这里出现了PoolManager这个类,从字面含义上看,自动释放就是将当前对象加到了对象列表管理器的一个对象列表里面,特别注意到这个函数里面并没有减少引用计数的操作

还有一个成员函数比较简单,就是返回当前对象的引用次数的值

unsigned int Ref::getReferenceCount() const
{
    return _referenceCount;
}

这个函数没什么可分析的,就是一个get方法。

分析到这里,引用机制有了进一步了解,小结一下有以下几点:

  1. Ref对象要在子类里面创建,不能直接实例化Ref
  2. Ref创建后的引用计数为1
  3. 使用retain来增加1 次对这个对象的引用 计数
  4. 调用 release时会减少一次对象的引用 计数,当引用计数为0的时候就会销毁这个对象。
  5. 用到了一个PoolManager对象列表管理器来管理Ref对象
  6. autorelease是通过内存管理器来释放对象的,并且对象调用 autorelease函数时并没有减少引用计数值

标红的5,6两点到目前我们只知道个大概,并不知道里面的具体机制是什么,所以下面跟小鱼来分析一下这个PoolManager类,看看这家伙到底干了些什么

PoolManager类在CCAutoreleasePool.h文件中定义的

这个文件里面定义了两个类

class CC_DLL AutoreleasePool
class CC_DLL PoolManager

Cocos2d-x的命名真是太好了,很大的程度上可以帮助我们来阅读代码,这里赞一个。

顾名思义

AutoreleasePool 是用来描述一个自动释放对象池结构的定义

PoolManager 是用来管理所有的AutoreleasePool的管理器。结构很像我们经常用到过的 线程定义与线程管理器定义、socket连接的定义与socket连接管理器的定义,新手读者可以学习这样的数据结构用到自己的程序中。

下面我们先看 AutoreleasePool类的定义

先看一下AutoreleasePool的成员变量

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

_managedObjectArray; 是用来保存这个自动释放池里面加入的所有Ref对象

std::string _name; 可以了解到每个自动释放池可以有一个名字,这个也方便我们查看和管理

bool _isClearing; 在debug模式下,用来标记当前这个对象列表是否已经做了清理操作。

通过这三个成员变量可以得知,这个类主要是操作_managedObjectArray这个vector来管理这个对象列表的所有对象,下面我们一个一个方法来分析,探究对象列表都有哪些管理操作。

从构造函数和析构函数开始

构造函数

AutoreleasePool::AutoreleasePool()
: _name("")
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0),_isClearing(false)
#endif
{
    _managedObjectArray.reserve(150);
    PoolManager::getInstance()->push(this);//创建AutoreleasePool对象时构造函数自动将此对象加入PoolManager,
// void PoolManager::push(AutoreleasePool *pool) ///{ // _releasePoolStack.push_back(pool); // _curReleasePool = pool; ///}
//}

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);
}

AutoreleasePool有两个构造函数

第一个没有参数的,做了两件事情,1. 将对象列表的容量扩展到了 150个对象。 2. 将当前的对象列表加入到了对象列表管理器的队列中

第二 个是带一个参数的构造函数,可以给要创建的对象列表起一个名字,其它的操作与第一个默认构造函数一样。

再看析构函数

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

析构函数也很简单,也是干了两件事

1. 执行了一次clear()方法

2. 将这个对象列表从自动释放对象列表管理器里面删除。 (疑惑1,调用 了 PoolManager::getInstance()->pop();方法,那么内存管理器里肯定有很多个对象列表,它怎么知道pop的一定是当前这个列表呢?这里面是不是有错误呢?这里究竟是怎么回事?后面我们看看能不能解决这个疑问)

接下来,我们来看一下,将Ref对象加入到对象列表的方法

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

这个函数很简单,就是将Ref*对象加到_managedObjectArray的最后面。

再看一下clear方法

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

这个函数主要还是干了两件事情

  1. 遍历对象列表里面的每一个Ref对象,调用 它的release方法,这块要注意,调用了release并不代表就彻底销毁了这个对象,我们上面分析过release方法会减少对象的引用计数,只会销毁引用计数为0的对象。
  2. 将所有对象的指针从对象列表里面清除。

这里面我们注意了,在debug模式下,会有一个_isClearing的状态操作,这个有点类似线程同步的数据锁的编程技巧,因为_managedObjectArray可能里面的对象很多,所以用这个_isClearing来将清除时的操作状态锁定,其它线程访问这个对象列表的时候,会根据这个对象列表的状态来决定是否立即引用这个对象列表里面的对象。

AutoreleasePool还提供了两个工具方法

bool AutoreleasePool::contains(Ref* object) const
{
    for (const auto& obj : _managedObjectArray)
    {
        if (obj == object)
            return true;
    }
    return false;
}

这个方法是用来检查指定的对象 object 是否在当前对象列表里面,返回 bool类型

void AutoreleasePool::dump()
{
    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());
    }
}

一看就知道 dump是个调试时用的方法,打印出当前对象列表的信息,包括名称,装载对象数量及每个对象的引用次数。游戏开发做性能分析及优化的时候肯定少不了用到这个函数。

到此我们看完了AutoreleasePool这个类的源码,我给这个类起的名字是,对象自动释放列表,不知道是否专业和恰当,希望众多读者指正。

小结一下

  1. AutoreleasePool 类有一个保存Ref对象的数组
  2. 每个AutoreleasePool可以有一个名字
  3. addObject 是 向对象列表加入一个Ref对象
  4. clear函数是 对AutoreleasePool中管理的每个Ref对象执行了一次release操作,并不一定会销毁Ref 对象,要看Ref的引用 计数是否到达了0
  5. contains 是查询指定的Ref对象是否在当前这个列表里面。
  6. dump 调试的函数,打印出对象列表里的对象状态和以对象列表信息。

这里面我们有一个疑惑就是在clear方法里面只是调用了 PoolManager的pop方法,而没有具体告诉PoolManager要消除哪个对象列表,这里面会不会出错。带着疑问我们来看一下PoolManager这个类

老方法,先看看PoolManager的成员变量

static PoolManager* s_singleInstance;
    
    std::deque<AutoreleasePool*> _releasePoolStack;
    AutoreleasePool *_curReleasePool;

s_singleInstance 用来实现单例的PoolManager对象

releasePoolStack 这是一个双向队列,来保存所有的 AutoreleasePool

AutoreleasePool *_curReleasePool; 从命名上看是当前的autoreleasePool,难道这个对象和我们的疑问有关吗?好像有点眉目了,继续看方法。

构造函数 空的,没啥可看的了,

析构函数

PoolManager::~PoolManager()
{
    CCLOGINFO("deallocing PoolManager: %p",this);
    
    while (!_releasePoolStack.empty())
    {
        AutoreleasePool* pool = _releasePoolStack.back();
        _releasePoolStack.pop_back();
        
        delete pool;//此时会delete引擎自动产生的2个一模一样的默认的自动释放对象列表
    }
}

析构函数干了一件事 遍历所有对象列表 从 _releasePoolStack里面出栈,并销毁这个对象列表。上面我们分析autoreleaesPool的时候知道 析构的时候会调用 clear方法,对每个对象进行引用计数及销毁操作。这里面我们注意到releasePoolStack是以栈的行为来使用的。

看到了栈那么我们就来看PoolManager的压栈和出栈方法。
压栈:

void PoolManager::push(AutoreleasePool *pool)
{
    _releasePoolStack.push_back(pool);
    _curReleasePool = pool;
}
  1. 将一个AutoreleasePool对象指针放到_releasePoolStack的末尾(栈顶)
  2. 将当前操作的的对象池指向新压入栈的pool

通过这里的代码我们可以判断,PoolManager同时只处理一个自动释放对象池,也就是在栈顶的那一个,那么上面我们的疑问,基本可以解决了,因为Pop的永远是_curReleasePool这个对象,也就是栈顶的一个元素。

出栈:

void PoolManager::pop()
{
    // Can not pop the pool that created by engine
    CC_ASSERT(_releasePoolStack.size() >= 1);
    
    _releasePoolStack.pop_back();
    
    // Should update _curReleasePool if a temple pool is released
    if (_releasePoolStack.size() > 1)
    {
        _curReleasePool = _releasePoolStack.back();
    }
}

干了两件事

  1. 弹出栈顶的一个对象池
  2. 如果栈不为空将当前处理的对象池指向新的栈顶对象。

疑问2: _curReleasePool = _releasePoolStack.back(); 的条件是 _releasePoolStack.size() >1 而不是 >= 1 这是为什么呢?如果releasePoolStack.size()==1 那么 curReleasePool 没有得到重新赋值,这不出现了野指针了吗?带着疑问继续找答案

这里有一个注释我们可以解读一下 不能弹出引擎自己创建的对象池。那么引擎自己创建的自动释放对象池是哪个呢?

我们看一下单例方法来寻找一下线索

PoolManager* PoolManager::getInstance()
{
    if (s_singleInstance == nullptr)
    {
        s_singleInstance = new PoolManager();//在PoolManager的析构函数中释放
        // Add the first auto release pool
        s_singleInstance->_curReleasePool = new AutoreleasePool("cocos2d autorelease pool");
        s_singleInstance->_releasePoolStack.push_back(s_singleInstance->_curReleasePool);
    }
    return s_singleInstance;
}

哈哈,果然在这里 单例方法创建PoolManager对象时还创建了一个 AutoreleasePool 对象 起的名字是 "cocos2d autorelease pool"也就是说只要引擎启动就会有一个默认的自动释放对象列表,这个列表被放到了 PoolManager的栈底。

再仔细阅读一下,可以注意到实际上首次调用这个函数得到单例的时候是放到了栈里面两个AutoreleasePool 第一个是 AutoreleasePool new构造函数的时候放进去的第二个是在s_singleInstance->_releasePoolStack.push_back(s_singleInstance->_curReleasePool);放进去的。因为这里面有两个AutoreleasePool对象,所以疑问2就迎刃而解了。

再看看剩下的几个函数。

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

销毁PoolManager 这个函数很简单标准的指针delete操作,没什么可研究的。

AutoreleasePool* PoolManager::getCurrentPool() const
{
    return _curReleasePool;
}

_curReleasePool的get方法。

bool PoolManager::isObjectInPools(Ref* obj) const
{
    for (const auto& pool : _releasePoolStack)
    {
        if (pool->contains(obj))
            return true;
    }
    return false;
}

遍历管理器里面所有的自动释放对象队列,寻找是否有obj对象,这里做的是指针的地址比较,返回bool类型。

回过头我们再看看AutoreleasePool这个类的构造函数和析构函数

在构造函数中有这么一行代码

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

这说明,每当我们创建一个 释放列表对象的时候,这个列表已经加入到了列表管理器里面此时的_curReleasePool指向了这个新创建的列表对象

在析构函数中还有一行代码

PoolManager::getInstance()->pop();

在这个列表对象销毁的时候,会将当前对象出栈操作。如果栈里面只剩下一个默认列表,那么_currReleasePool并不会重新指向它。

从AutoreleasePool的构造和析构来分析,我们可以推断在使用cocos2d-x的内存管理的时候 不需要显示的操作 列表管理器PoolManager的对象,只要重新创建一个AutoreleasePool对象就可以了。

我们再看一下AutoreleasePool构造函数前面有一段注释。

/**
     * @warn Don't create an auto release pool in heap,create it in stack.
     * @js NA
     * @lua NA
     */
    AutoreleasePool();

终于理解了这段注释的意思,不要在堆中创建 pool 要在栈中创建, 说白一点,就是要控制 AutoreleasePool的作用域,不要new这个对象。

结合上面没有翻译的那一大段Ref引用计数使用方法的注释这里总结一下 Ref的引用计数 AutoreleasePool 及 PoolManager这三个类的工作方式及使用方法

  1. Ref必须被子类继承来使用不能直接创建Ref的实例对象,因为它的构造函数是protected的访问类型
  2. 当Ref的对象被创建的时候它的引用计数为1
  3. Ref::release调用时会立刻减少引用计数而 Ref::autorelease 不会立刻减少引用计数,它将Ref对象加入到当前的自动释放对象池中去统一的时机来进行释放,疑问3:到底arutrelease在什么时候被引擎调用的呢?
  4. Ref对象创建后可以调用一次release 或 autorelease来释放,若多次引用那么要 retain 和 release/autorelease 成对的出现,否则就会出现引用次数不正确的情况,在dubug模式下,release是有引用次数检测的,会打印出assert信息。
  5. 不需要我们来操作PoolManager这个单例对象,PoolManager是引擎内部来管理的
  6. 引擎初始化后就会创建一个默认的自动释放对象列表并加入到了PoolManager里面进行管理,这个默认对象列表是不会被自动机制清除的,只 有在析构的时候才能销毁。
  7. autoreleasePool要在栈上创建,也就是要使用局部变量创建。不要new来创建。
  8. 还要注意一点在引擎初始化的时候实际上默认的有两个cocos2d autorelease pool对象列表 这也是为什么 PoolManager::Pop方法里面为什么_currReleasePool不会出现野指针的关键所在。
    在这里小鱼想给cocos2d-x这块的代码优化一下,这里各位读者也一起提点见意
PoolManager* PoolManager::getInstance()
{
    if (s_singleInstance == nullptr)
    {
        s_singleInstance = new PoolManager();
        // Add the first auto release pool
        s_singleInstance->_curReleasePool = new AutoreleasePool("cocos2d autorelease pool");
        //s_singleInstance->_releasePoolStack.push_back(s_singleInstance->_curReleasePool);
    }
    return s_singleInstance;
}
void PoolManager::pop()
{
    // Can not pop the pool that created by engine
    CC_ASSERT(_releasePoolStack.size() >= 1);
    CC_ASSERT(_releasePoolStack.size() > 1 );     
    _releasePoolStack.pop_back();
    
    // Should update _curReleasePool if a temple pool is released
    if (_releasePoolStack.size() > 0) //这里改成0
    {
        _curReleasePool = _releasePoolStack.back();
    }
}

带着疑问3 我们来思考一下,当定义一个局部的autoreleasePool变量时

{

   AutoreleasePool pool1;
   ………………

}

这个pool1的作用域只在这对花括号中间,此时PoolManager里面的currReleasePool就是这个pool1,当程序执行完花括号的内容后 pool1的作用域到期,调用了pool1的析构函数 再回忆一下 AutoreleasePool的析构函数

this); clear(); PoolManager::getInstance()->pop(); }

调用了clear 在clear里面对所有在管理列表的Ref 对象调用了release减少了一次引用计数,并销毁那些引用计数为0的对象。

调用了PoolManager::pop方法, 调用这个方法后将当前的这个内存对象列表从管理器里面删除掉。

这样在这个局部作用域之间所有的内存管理实际上是交给了pool1来完成的,真是好方法,使用简单方便。

那么默认的自动释放管理是在哪里调用 的呢? 回顾一下上一章节,我们在看Director类的时候在主循环里面有这样一行代码。

void DisplayLinkDirector::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();
    }
    else if (! _invalid)
    {
        drawScene();
     
        // release the objects
        
PoolManager::getInstance()->getCurrentPool()->
clear();
    }
}
看到标红的那行代码,多让人兴奋,在每次主循环的最后都会主动的去释放一次自动管理的对象。
到此疑问3也解决了,临时的自动释放对象池在对象池的作用域内有效,引擎默认的自动释放对象池是在每一次逻辑帧的最后被调用和释放的。
现在,Ref、AutoreleasePool、PoolManager这三个类的源码我们已经分析完成了,并且针对 PoolManager类还提出了一个小小的优化方案。
回过本章节的开始,我们留了一个类放到最后来阅读一下。那就是Clonable
class CC_DLL Clonable
{
public:
    /** returns a copy of the 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;
    }
};

可以看到这个类是一个抽象类,提供了一个抽象方法 virtual Clonable* clone()const = 0; 显而易见这是让我们在想提供clone方法(对象复制方法)的类统一继续这个父类,不同的继承类要实现自己特有的clone方法

我在工程中随便搜索了一下,看看有没有具体用到clone方法的类,果然找到了一些,这里贴一个cocos2d-x中的一个类的代码从使用中来学习 Clonable这个类和总结一下今天所啰嗦的这点事。

class CC_DLL __Bool : public Ref,public Clonable
{
public:
    __Bool(bool v)
        : _value(v) {}
    bool getValue() const {return _value;}

    static __Bool* create(bool v)
    {
        __Bool* pRet = new __Bool(v);
        if (pRet)
        {
            pRet->autorelease();
        }
        return pRet;
    }

    /* override functions */
    virtual void acceptVisitor(DataVisitor &visitor) { visitor.visit(this); }

    __Bool* clone() const
    {
        return __Bool::create(_value);
    }
private:
    bool _value;
};

这是一个bool数据类型的定义(我虎躯一震,连数据类型都有定义啊,果然这个引擎很强大)

可以看到 这个bool类型继承了 Ref(这是要放到内存管理器里面统一管理啊,并且在这个bool类型创建的时候就已经使用到了autorelease来做内存释放,学到啦哈哈)

这个类还继承了 Clonable这个类,实现 了clone函数 ,实现的很简单就是重新创建了一个与当前值相同的对象,返回了新的对象引用。

哈哈,我越发我举的这个类太恰当了,又有内存管理的应用,又有clone方法的使用。

同理其它cocos2d-x的数据类型大多也都是这种创建模式,不要直接去new对象,提供一个静态方法create来创建对象,并且新创建的对象直接加入到自动的内存管理里面,来实现自动释放,这样避免了你到处去想着delete。安全方便。

本章节就到这里, 下一章节,我们看一下出场率很高的 Node 类。



版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


    本文实践自 RayWenderlich、Ali Hafizji 的文章《How To Create Dynamic Textures with CCRenderTexture in Cocos2D 2.X》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.1.4进行学习和移植。在这篇文章,将会学习到如何创建实时纹理、如何用Gimp创建无缝拼接纹
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@163.com微信公众号:HopToad 欢迎转载,转载标注出处:http://blog.csdn.netotbaron/article/details/424343991.  软件准备 下载地址:http://cn.cocos2d-x.org/download 2.  简介2.1         引用C
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从Cocos2D-x官网上下载,进入网页http://www.cocos2d-x.org/download,点击Cocos2d-x以下的Download  v3.0,保存到自定义的文件夹2:从python官网上下载。进入网页https://www.python.org/downloads/,我当前下载的是3.4.0(当前最新
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发引擎,易学易用,支持多种智能移动平台。官网地址:http://cocos2d-x.org/当前版本:2.0    有很多的学习资料,在这里我只做为自己的笔记记录下来,错误之处还请指出。在VisualStudio2008平台的编译:1.下载当前稳
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《最强大脑》娱乐节目。将2048改造成一款挑战玩家对数字记忆的小游戏。邮箱:appdevzw@163.com微信公众号:HopToadAPK下载地址:http://download.csdn.net/detailotbaron/8446223源码下载地址:http://download.csdn.net/
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试以QtCreatorIDE来进行CMake构建。Cocos2d-x3.X地址:https://github.com/cocos2d/cocos2d-x1.打开QtCreator,菜单栏→"打开文件或项目...",打开cocos2d-x目录下的CMakeLists.txt文件;2.弹出CMake向导,如下图所示:设置
 下载地址:链接:https://pan.baidu.com/s/1IkQsMU6NoERAAQLcCUMcXQ提取码:p1pb下载完成后,解压进入build目录使用vs2013打开工程设置平台工具集,打开设置界面设置: 点击开始编译等待编译结束编译成功在build文件下会出现一个新文件夹Debug.win32,里面就是编译
分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net前言上次用象棋演示了cocos2dx的基本用法,但是对cocos2dx并没有作深入的讨论,这次以超级马里奥的源代码为线索,我们一起来学习超级马里奥的实
1. 圆形音量button事实上作者的本意应该是叫做“电位计button”。可是我觉得它和我们的圆形音量button非常像,所以就这么叫它吧~先看效果:好了,不多解释,本篇到此为止。(旁白: 噗。就这样结束了?)啊才怪~我们来看看代码:[cpp] viewplaincopyprint?CCContro
原文链接:http://www.cnblogs.com/physwf/archive/2013/04/26/3043912.html为了进一步深入学习贯彻Cocos2d,我们将自己写一个场景类,但我们不会走的太远,凡是都要循序渐进,哪怕只前进一点点,那也至少是前进了,总比贪多嚼不烂一头雾水的好。在上一节中我们建
2019独角兽企业重金招聘Python工程师标准>>>cocos2d2.0之后加入了一种九宫格的实现,主要作用是用来拉伸图片,这样的好处在于保留图片四个角不变形的同时,对图片中间部分进行拉伸,来满足一些控件的自适应(PS: 比如包括按钮,对话框,最直观的形象就是ios里的短信气泡了),这就要求图
原文链接:http://www.cnblogs.com/linji/p/3599478.html1.环境和工具准备Win7VS2010/2012,至于2008v2版本之后似乎就不支持了。 2.安装pythonv.2.0版本之前是用vs模板创建工程的,到vs2.2之后就改用python创建了。到python官网下载版本2.7.5的,然后
环境:ubuntu14.04adt-bundle-linux-x86_64android-ndk-r9d-linux-x86_64cocos2d-x-3.0正式版apache-ant1.9.3python2.7(ubuntu自带)加入环境变量exportANDROID_SDK_ROOT=/home/yangming/adt-bundle-linux/sdkexportPATH=${PATH}:/$ANDROID_SDK_ROOTools/export
1开发背景游戏程序设计涉及了学科中的各个方面,鉴于目的在于学习与进步,本游戏《FlappyBird》采用了两个不同的开发方式来开发本款游戏,一类直接采用win32底层API来实现,另一类采用当前火热的cocos2d-x游戏引擎来开发本游戏。2需求分析2.1数据分析本项目要开发的是一款游
原文链接:http://www.cnblogs.com/linji/p/3599912.html//纯色色块控件(锚点默认左下角)CCLayerColor*ccc=CCLayerColor::create(ccc4(255,0,0,128),200,100);//渐变色块控件CCLayerGradient*ccc=CCLayerGradient::create(ccc4(255,0,0,
原文链接:http://www.cnblogs.com/linji/p/3599488.html//载入一张图片CCSprite*leftDoor=CCSprite::create("loading/door.png");leftDoor->setAnchorPoint(ccp(1,0.5));//设置锚点为右边中心点leftDoor->setPosition(ccp(240,160));/
为了答谢广大学员对智捷课堂以及关老师的支持,现购买51CTO学院关老师的Cocos2d-x课程之一可以送智捷课堂编写图书一本(专题可以送3本)。一、Cocos2d-x课程列表:1、Cocos2d-x入门与提高视频教程__Part22、Cocos2d-x数据持久化与网络通信__Part33、Cocos2d-x架构设计与性能优化内存优
Spawn让多个action同时执行。Spawn有多种不同的create方法,最终都调用了createWithTwoActions(FiniteTimeAction*action1,FiniteTimeAction*action2)方法。createWithTwoActions调用initWithTwoActions方法:对两个action变量初始化:_one=action1;_two=action2;如果两个a
需要环境:php,luajit.昨天在cygwin上安装php和luajit环境,这真特么是一个坑。建议不要用虚拟环境安装打包环境,否则可能会出现各种莫名问题。折腾了一下午,最终将环境转向linux。其中,luajit的安装脚本已经在quick-cocos2d-x-develop/bin/中,直接luajit_install.sh即可。我的lin
v3.0相对v2.2来说,最引人注意的。应该是对触摸层级的优化。和lambda回调函数的引入(嗯嗯,不枉我改了那么多类名。话说,每次cocos2dx大更新。总要改掉一堆类名函数名)。这些特性应该有不少人研究了,所以今天说点跟图片有关的东西。v3.0在载入图片方面也有了非常大改变,仅仅只是