之前看过很多的文章,都是讲解cocos2dx的引用计数原理。有好有坏,今天我也自己来写一篇引用计数的文章,并且开始尝试阅读cocos2dx的源码,把自己的收获更新到这个博客,不为多少人看。只为自己记录这一路的点点滴滴。
讲了这么多那么开始吧!
首先就是贴上cocos2dx的万物之源 Ref类,这个类是Node的基类,而所有的显示节点类都是继承自Node类,所以他算是cocos2dx的根。
CCRef.h
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; #if CC_ENABLE_SCRIPT_BINDING public: unsigned int _ID; int _luaID; void* _scriptObject; #endif #if CC_USE_MEM_LEAK_DETECTION public: static void printLeaks(); #endif };CCRef.cpp
#include "base/CCRef.h" #include "base/CCAutoreleasePool.h" #include "base/ccMacros.h" #include "base/CCScriptSupport.h" #if CC_USE_MEM_LEAK_DETECTION #include <algorithm> // std::find #endif NS_CC_BEGIN #if CC_USE_MEM_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 static unsigned int uObjectCount = 0; _luaID = 0; _ID = ++uObjectCount; _scriptObject = nullptr; #endif #if CC_USE_MEM_LEAK_DETECTION trackRef(this); #endif } 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 != nullptr && pEngine->getScriptType() == kScriptTypeJavascript) { pEngine->removeScriptObjectByObject(this); } } #endif #if CC_USE_MEM_LEAK_DETECTION if (_referenceCount != 0) untrackRef(this); #endif } void Ref::retain() { CCASSERT(_referenceCount > 0,"reference count should greater than 0"); ++_referenceCount; } 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)) { CCASSERT(false,"The reference shouldn't be 0 because it is still in autorelease pool."); } #endif #if CC_USE_MEM_LEAK_DETECTION untrackRef(this); #endif delete this; } } Ref* Ref::autorelease() { PoolManager::getInstance()->getCurrentPool()->addObject(this); return this; } unsigned int Ref::getReferenceCount() const { return _referenceCount; } #if CC_USE_MEM_LEAK_DETECTION static std::list<Ref*> __refAllocationList; void Ref::printLeaks() { 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) { CCASSERT(ref,"Invalid parameter,ref should not be null!"); __refAllocationList.push_back(ref); } static void untrackRef(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; } __refAllocationList.erase(iter); } #endif // #if CC_USE_MEM_LEAK_DETECTION NS_CC_END
我们从一看出Ref这个类中有一个
protected: //用于计数的变量 unsigned int _referenceCount;这个变量就是记录引用次数,并且在构造函数中初始化时1。
重要的来了,那就是release这个函数,我们看到他是在计数器为0的时候,delete自己。这样的设计源于自己创建自己销毁这个原则。
我们还可以看到 autoRelease 这个接口,他调用了PoolManager。
从字面上猜到,这个接口的作用应该是自动释放。
那么他是怎么实现的呢?
我们又找到了PoolManager的源码。
CCAutoreleasePool.h
#ifndef __AUTORELEASEPOOL_H__ #define __AUTORELEASEPOOL_H__ #include <stack> #include <vector> #include <string> #include "base/CCRef.h" NS_CC_BEGIN class CC_DLL AutoreleasePool { public: AutoreleasePool(); AutoreleasePool(const std::string &name); ~AutoreleasePool(); void addObject(Ref *object); void clear(); #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) bool isClearing() const { return _isClearing; }; #endif bool contains(Ref* object) const; void dump(); private: std::vector<Ref*> _managedObjectArray; 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(); } static PoolManager* getInstance(); CC_DEPRECATED_ATTRIBUTE static void purgePoolManager() { destroyInstance(); } 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; }; NS_CC_END #endif //__AUTORELEASEPOOL_H__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) #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); } AutoreleasePool::~AutoreleasePool() { CCLOGINFO("deallocing AutoreleasePool: %p",this); clear(); PoolManager::getInstance()->pop(); } void AutoreleasePool::addObject(Ref* object) { _managedObjectArray.push_back(object); } 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 } bool AutoreleasePool::contains(Ref* object) const { for (const auto& obj : _managedObjectArray) { if (obj == object) return true; } return false; } 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()); } } //-------------------------------------------------------------------- // // PoolManager // //-------------------------------------------------------------------- PoolManager* PoolManager::s_singleInstance = nullptr; PoolManager* PoolManager::getInstance() { if (s_singleInstance == nullptr) { s_singleInstance = new 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::~PoolManager() { CCLOGINFO("deallocing PoolManager: %p",this); while (!_releasePoolStack.empty()) { AutoreleasePool* pool = _releasePoolStack.back(); delete pool; } } AutoreleasePool* PoolManager::getCurrentPool() const { return _releasePoolStack.back(); } bool PoolManager::isObjectInPools(Ref* obj) const { for (const auto& pool : _releasePoolStack) { if (pool->contains(obj)) return true; } return false; } void PoolManager::push(AutoreleasePool *pool) { _releasePoolStack.push_back(pool); } void PoolManager::pop() { CC_ASSERT(!_releasePoolStack.empty()); _releasePoolStack.pop_back(); } NS_CC_END可以看到
AutoreleasePool* PoolManager::getCurrentPool()返回了一个AutoreleasePool的指针,<pre name="code" class="cpp">AutoreleasePool这个指针将在Ref autoRelease的时候添加到了一个叫<pre name="code" class="cpp">_managedObjectArray的vector里面去了。
那么自动管理是怎么做到呢?
转到CCDirector.cpp
<pre name="code" class="cpp">void DisplayLinkDirector::mainLoop() { if (_purgeDirectorInNextLoop) { _purgeDirectorInNextLoop = false; purgeDirector(); } else if (! _invalid) { drawScene(); // release the objects PoolManager::getInstance()->getCurrentPool()->clear(); } }
在这个文件的上面那个函数中,我们可以看到每个loop里面在渲染场景之后PoolManager做了一个操作,这个操作就是AutoreleasePool类里面的clear,
这个clear就是将维护的_managedObjectArray里面的每个对象执行release操作,最后把_managedObjectArray清空。
<span style="font-family: Arial,Helvetica,sans-serif;">看到这里我们大约知道了他的原理了。</span>
1、我们在创建Ref的时候计数器值是1;
2、执行retain的时候计数器加1,执行release的时候计数器减1,当计数器为0时由对象自己删除自己。
3、当我们执行autorelease的时候,Ref把释放的操作的权利给了AutoreleasePool这个友元类,就是如果我们没有任何retain操作,这个Ref的对象将在这一帧结束后被AutoreleasePool的clear给释放掉。
4、当我们在创建后又retain,那么将保留对这个Ref对象的控制权,只有在你release后才会被释放,因为AutoreleasePool只执行一次便不再参与该对象的内存管理。
<span style="font-family: Arial,sans-serif;"> </span>
<span style="font-family: Arial,sans-serif;">差不多我了解的cocos2dx引用计数的原理就是这么多了,博主的水平也是非常有限,哪里写得不好或出错,欢迎指正交流。</span>