一. 什么是引用计数
在Cocos2d-x中,则定义了retain,release和autorelease函数,分别用于增加计数、减少计数以及将一个对象交给自动释放池对象AutoreleasePool进行管理,由AutoreleasePool对象负责调用release函数。
二. 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
#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内存管理的运行原理
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的内置类都是通过它来进行管理的。