cocos2d-x源码剖析-2-万物皆可Ref

Ref类

如果把Cocos2d比喻为一颗大树,那么Ref类可以说是这颗大树的根,Cocos2d中大部分类的源头都是Ref类,也就是说Ref是一个祖先类。它的主要作用是使用引用计数来管理资源,有点像shared_ptr。我们把其中最重要的代码截出来,如下所示。

class Ref
{
public:
    void retain();

    void release();

    Ref* autorelease();

    unsigned int getReferenceCount() const;

protected:
    Ref();

public:
   
    virtual ~Ref();

protected:
    unsigned int _referenceCount;

    friend class AutoreleasePool;
};

Ref()

Ref::Ref()
: _referenceCount(1) 
{
}

Ref在构造函数的时候将引用计数设置为1。

~Ref()

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


#if CC_REF_LEAK_DETECTION
    if (_referenceCount != 0)
        untrackRef(this);
#endif
}

Ref在析构时移除了脚本(如果你是用脚本语言写的话)。

retain()

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

在调用retain()时会判断引用计数是否大于零,如果否则启动断言,如果是则程序继续执行,然后引用计数加一。这个函数用来增加引用计数且每次调用只增加1。

release()

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_ENABLE_SCRIPT_BINDING
        ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
        if (pEngine != nullptr && pEngine->getScriptType() == kScriptTypeJavascript)
        {
            pEngine->removeObjectProxy(this);
        }
#endif // CC_ENABLE_SCRIPT_BINDING

#if CC_REF_LEAK_DETECTION
        untrackRef(this);
#endif
        delete this;
    }
}

release是retain的反向操作,它会将引用计数加一,但是它同时还兼顾着其它的责任,来逐句分析:

  1. if (_referenceCount == 0)                                                        // 当引用计数为零时触发此if下代码
  2. #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) //由于COCOS2D_DEBUG定义为1,永为真,所以条件一定触发
  3. auto poolManager = PoolManager::getInstance()          //PoolManager是cocos2d的内存池管理器,是一个单例类
  4. if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this)) //此if用来判断内存池是否为空,并且判断不为空的内存池内是否存在当前对象,如果内存池不为空,且当前对象在内存池内,会触发中断
  5. CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.")    //触发中断当前引用计数不应当为零,因为当前对象依旧存在与自动释放内存池中。
  6. CC_ENABLE_SCRIPT_BINDING部分不再赘述,用来移除绑定脚本
  7. delete this 如果没有触发断言中断则程序最终会到这里,销毁对象并释放内存

 

autorelease()

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

从代码可以看出,此函数将当前对象放入了当前自释放内存池管理器。

 

getReferenceCount()

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

当前引用计数的get方法,用来获得当前引用计数。

 

总结

Ref的主要代码就已经解析完了,那么Ref的具体作用是什么呢?我们可以来看一个场景,一个游戏角色从A地图传送到B地图,在代码里面就是一个Sprite在两个Scene里面传递,这是我们使用的一定是引用传递,然后人物如果再返回A地图,那么当前B地图就要将资源释放,游戏人物也将一起释放,由于是引用传递,那么人物的内存也将释放,这是在A地图中人物就不存在了。所以为了防止这种情况发生,就要用到引用计数,每次涉及到引用传递时就将计数加一,释放引用内存时先将引用计数减一,只有当引用计数为0时再释放内存,否则只减少引用计数而不释放内存。

cocos2d-x源码剖析-2-万物皆可Ref_第1张图片

但是内存管理并不是只有引用计数就行了,还得搭配智能内存池才可以,下一节中将会进行解析。

 

扩展

我们再Ref类还看到一段有意思的代码,它被CC_REF_LEAK_DETECTION宏控制着,我们如果需要使用可以把CC_REF_LEAK_DETECTION宏设置为1,这段代码被用来进行泄露检测,函数部分其实在Ref的成员函数中也出现过,但是为了方便解析就没有提及,现在我们来把他单独拿出来,把宏控制的代码块单独来看。

static std::vector __refAllocationList;
static std::mutex __refMutex;

void Ref::printLeaks()
{
    std::lock_guard refLockGuard(__refMutex);
    // Dump Ref object memory leaks
    if (__refAllocationList.empty())
    {
        log("[memory] All Ref objects successfully cleaned up (no leaks detected).\n");
    }
    else
    {
        log("[memory] WARNING: %d Ref objects still active in memory.\n", (int)__refAllocationList.size());

        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)
{
    std::lock_guard refLockGuard(__refMutex);
    CCASSERT(ref, "Invalid parameter, ref should not be null!");

    // Create memory allocation record.
    __refAllocationList.push_back(ref);
}

static void untrackRef(Ref* ref)
{
    std::lock_guard refLockGuard(__refMutex);
    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;
    }

    __refAllocationList.erase(iter);
}

 代码使用了mutex锁说明是线程安全的。

 

static std::vector __refAllocationList

__refAllocationList是用来存储Ref的分配链表。

 

printLeaks()

用来打印泄露信息,如果__refAllocationList为空那么就不存在泄露,如果不为空就打印泄露的对象。

为空:

if (__refAllocationList.empty())
    {
        log("[memory] All Ref objects successfully cleaned up (no leaks detected).\n");
    }

不为空:

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

 

trackRef(Ref* ref)

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

    // Create memory allocation record.
    __refAllocationList.push_back(ref);
}

 向__refAllocationList中存入Ref

 

untrackRef(Ref* ref)

static void untrackRef(Ref* ref)
{
    std::lock_guard refLockGuard(__refMutex);
    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;
    }

    __refAllocationList.erase(iter);
}

 如果__refAllocationList中存在传入的Ref就删除。

 

我们可以看见在Ref的构造函数Ref::Ref()中调用了一次trackRef(this),说明把当前对象存入了__refAllocationList中,在析构函数Ref::~Ref()中调用了一次untrackRef(this),说明要想从__refAllocationList中删除就必须彻底释放此对象,否则就存在泄露的可疑,这样泄露检测就完成了,可以说是一个很巧妙的设计。

你可能感兴趣的:(cocos2d-x)