cocos2dx学习之路----第十四篇(内存管理机制)

本来这一篇要继续详谈一下动作类相关的应用,但是考虑到思路差不多,只是调用的类方法名参数不一样而已。到时候再通过一个小的案例来进一步熟悉学习相关接口的使用。这一篇就来谈谈另外一个比较重要的类,引用计数类(Ref)。

关于cocos2dx这一套内存管理,它是引用了object-c的这么一套风格,即通过引用计数类来判断是否要进行相关资源的回收释放。

具体的用法很简单,当物体被创建时给予一个初始的引用计数1,当在每一帧结束时,我们会对场景中所有对象进行引用计数的释放操作,如果释放后引用计数变为零,则删除该对象。可以通过调用相关的保留或者释放方法对当前场景对象进行引用计数的增减。

好,以下就是Ref类相关的函数:

class CC_DLL Ref
{
public:
    void retain();//增加引用计数
    void release();//减少引用计数
    Ref* autorelease();
    unsigned int getReferenceCount() const;//获取当前对象引用计数
protected:
    Ref();
public:
    virtual ~Ref();
protected:
    unsigned int _referenceCount;
    friend class AutoreleasePool;
};
通过上面我们会奇怪,为什么还会有一个友元类AutoreleasePool呢?

其实,这就是cocos的真正实现的对象的管理类。

如果单单是对对象进行简单的计数,那是远远不够的,因为你知道什么时候加一,但是往往却会忽略了什么时候该去释放。这时候就加入了对象池的概念。通过调用autorelease()方法让它自动去判断,这便会方便我们去把多一些时间用在游戏逻辑的开发上。

好,我们再来看看autorelease具体实现了什么:

Ref* Ref::autorelease()
{
    PoolManager::getInstance()->getCurrentPool()->addObject(this);
    return this;
}
此时它是调用了叫PoolManager这一单例类的方法得到当前对象池,然后再往对象池添加调用此方法的对象。

这是又有一个问题出现了,PoolManager类又是什么?又实现了什么功能?

它其实就是对象池的管理者,其实cocos中默认是使用一个对象池进行对象的管理的,也就是场景中的所有对象都被存放于此,一般游戏开发中,对于场景中需要创建对象太多或是频繁的情况,往往我们需要自己定义属于该类对象的对象池,这是很有必要的,因为如果每一次都是动态的创建和删除,那将很耗CPU内存。而这篇文章先让我们大概了解一个关于cocos中对于对象池的一些概念以及使用。

好,我们先简单的来看看AutoreleasePool和PoolManager具体实现那些方法:

class CC_DLL AutoreleasePool
{
public:
    AutoreleasePool();
    AutoreleasePool(const std::string &name);//给定名字创建对象池
    ~AutoreleasePool();
    void addObject(Ref *object);
    void clear();
    bool contains(Ref* object) const;
    void dump(); 
private:
    std::vector<Ref*> _managedObjectArray;//对象容器
    std::string _name;
};

class CC_DLL PoolManager
{
public:
    static PoolManager* getInstance();
    static void destroyInstance();
    AutoreleasePool *getCurrentPool() const;
    bool isObjectInPools(Ref* obj) const;
    friend class AutoreleasePool;
    
private:
    PoolManager();
    ~PoolManager();
    void push(AutoreleasePool *pool);
    void pop();
    static PoolManager* s_singleInstance;
    std::vector<AutoreleasePool*> _releasePoolStack;//对象池容器
};
我们会发现,AutoReleasePool类是PoolManager类友元类。这样是为了方便对对象池中的对象进行访问,访问该对象是否在对象池中。

好,虽然我们没看具体的实现,但是我觉得思路至少应该是要明确的,我们再来看看下面这张图:



对象池管理类也好,对象池类也罢,其实都属于容器类vector存储,是动态的栈,由上图可直观的看出。栈的特点便是先进的后出。通过上图可以直观的看到对象在对象池中是处于什么位置,而对象池又是如何被PoolManager存储的。

在函数块中每每新增一个对象池,便会通过PoolManager的单例来进行入栈处理的操作,此时,如果在该函数块中又产生新的对象,则对象将被压入到新增的对象池中。我们可以来看下AutoreleasePool构造函数:

AutoreleasePool::AutoreleasePool()
: _name("")
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
, _isClearing(false)
#endif
{
    _managedObjectArray.reserve(150);
    PoolManager::getInstance()->push(this);//把对象池压入对象池容器进行进栈操作
}
然后我们再看到获取当前对象池的方法:

AutoreleasePool* PoolManager::getCurrentPool() const
{
    return _releasePoolStack.back();
}
直接就返回了最后一次进栈的对象池。所以,在对象调用autorelease方法时,此时的对象池便是你最新定于的对象池,所以被添加的也就是最新定于的对象池。

最后,我们可以再看下每一帧循环结束时,是怎么进行对象引用计数的检查的。

看到main.cpp,然后找到run方法转到定义,可以看到如下代码:

if (glview->isOpenGLReady())
    {
        director->end();
        director->mainLoop();
        director = nullptr;
    }
转到mainLoop方法,这是游戏的主循环,所以我们继续转到定义进去看看:

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();
    }
}
此时,我们就看到了关于对象管理类相关方法的调用了,这里直接获取当前对象池然后直接调用clear方法。

这里就是在每一帧结束后所做的事情。好,我们再看到对象管理类中的clear方法具体实现了什么,转到定义:

void AutoreleasePool::clear()
{
    std::vector<Ref*> releasings;
    releasings.swap(_managedObjectArray);
    for (const auto &obj : releasings)
    {
        obj->release();
    }
}
其实,就是定义一个新的对象池,然后把当前对象池中所有对象转移上去,然后对每一个对象池中的对象进行release方法调用,而这一方法就是是当前对象的引用计数减1,如果减一之后变为0,那么,直接删除该对象。

现在,我们来做一个简单的测试,来进一步熟悉,我先把代码贴出来:

ReferenceTest.h:

#ifndef __REFERENCE_TEST_H__
#define __REFERENCE_TEST_H__

#include <string>
#include"cocos2d.h"
using namespace cocos2d;

class ReferenceTest :public Scene{
public:
	virtual bool init();
	CREATE_FUNC(ReferenceTest);
};
//测试类
class RefTestObject :public Ref{
public:
	RefTestObject() :_name(""){};
	RefTestObject(std::string name) :_name(name){
		CCLOG("TestObject:%s is create.", _name.c_str());
	};
	~RefTestObject(){
		if (_name.size() > 0){
			CCLOG("TestObject:%s is destroyed.", _name.c_str());
		}
	};

private:
	std::string _name;
};
#endif
ReferenceTest.cpp:

#include"ReferenceTest.h"

bool ReferenceTest::init(){
	if (!Scene::init()){
		return false;
	}
	auto winSize = Director::getInstance()->getWinSize();

	//当前测试标签描述
	auto test_label = Label::createWithSystemFont("ReferenceTest Test", "", 30);
	test_label->setPosition(Vec2(winSize.width / 2, winSize.height - test_label->getContentSize().height));
	this->addChild(test_label);
<span style="white-space:pre">	</span>//创建对象,命名为testobj
	RefTestObject *obj = new(std::nothrow)RefTestObject("testobj");
	obj->autorelease();
<span style="white-space:pre">	</span>//autorelease可以多次调用,但是在调用之前,需要retain一下
	obj->retain();
	obj->autorelease();
	{
		AutoreleasePool pool2;//新增对象池,此时对象会被创建在对象池pool2中
		char name[20];
		for (int i = 0; i < 100; ++i)
		{
			snprintf(name, 20, "object%d", i);
			RefTestObject *tmpObj = new (std::nothrow) RefTestObject(name);
			tmpObj->autorelease();
		}
	}
	{
		PoolManager::destroyInstance();//可以删除对象池中所有对象,此时并无具体对象被删除操作
	}
	return true;
}
结果如下:

TestObject:testobj is create.
TestObject:object0 is create.
TestObject:object1 is create.
TestObject:object0 is destroyed.
TestObject:object1 is destroyed.
TestObject:testobj is destroyed.







  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值