Ref
Ref
是cocos2d里面所有类型的基类,用于引用计数。
-
Ref::Ref()
:构造函数的初始化列表会将该对象的引用计数赋值为1(Ref::Ref(): _referenceCount(1)
)。 -
void Ref::retain()
:将引用计数+1。 -
void Ref::release()
:将引用计数-1。如果引用计数为0则删除该对象(delete this
)。#if代码块的意思是为了确保如果当前对象在自动释放池中(poolManager->isObjectInPools(this)
),那么只有在池子在进行清理时(poolManager->getCurrentPool()->isClearing()
)才能被释放,否则就断言不通过。void Ref::release() { --_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 delete this; } }
-
Ref* Ref::autorelease()
:将对象添加到自动释放池中。 -
unsigned int Ref::getReferenceCount() const
:获取引用计数。 -
CCPlatformMacros.h
头文件中有一些关于指针相关操作的宏定义:#define CC_SAFE_DELETE(p) do { delete (p); (p) = nullptr; } while(0) #define CC_SAFE_DELETE_ARRAY(p) do { if(p) { delete[] (p); (p) = nullptr; } } while(0) #define CC_SAFE_FREE(p) do { if(p) { free(p); (p) = nullptr; } } while(0) #define CC_SAFE_RELEASE(p) do { if(p) { (p)->release(); } } while(0) #define CC_SAFE_RELEASE_NULL(p) do { if(p) { (p)->release(); (p) = nullptr; } } while(0) #define CC_SAFE_RETAIN(p) do { if(p) { (p)->retain(); } } while(0)
-
引用计数管理
- 有开始就有结束。对于
new
或copy
创建的对象,一旦不再使用,要调用release()
来释放。 - 有引用就有释放。被引用时调用
retain()
增加引用计数,不再被引用时也要调用release()
减少引用计数。 - 参数传递更替引用。当对象指针作为参数传递时,如果在函数内被其他对象所引用,则需要先释放旧的引用对象,在增加新的对象引用。e.g.
还有一个更为常见的例子就是void NodeGrid::setGrid(GridBase *grid) { CC_SAFE_RELEASE(_nodeGrid); CC_SAFE_RETAIN(grid); _nodeGrid = grid; }
Node
的addChild
和removeChild
方法在实现的过程中已经进行了相应的retain
和release
操作。从addChild
的函数调用栈可以看到最终在CCVector::pushBack
进行了retain
操作;removeChild
也是类似的过程。void Node::addChild(Node *child, int zOrder) -> void Node::addChild(Node* child, int localZOrder, const std::string &name) -> void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag) -> void Node::insertChild(Node* child, int z) -> void CCVector::pushBack(T object) { CCASSERT(object != nullptr, "The object should not be nullptr"); _data.push_back( object ); object->retain(); }
- 有开始就有结束。对于
-
自动释放池
- 调用
autoRelease
方法会将当前对象加入到自动释放池AutoreleasePool
中。
一般而言,各类型在使用静态的Ref* Ref::autorelease() { PoolManager::getInstance()->getCurrentPool()->addObject(this); return this; }
create
方法创建实例时就会调用其autoRelease
方法。e.g.Node * Node::create() { Node * ret = new (std::nothrow) Node(); if (ret && ret->init()) { ret->autorelease(); } else { CC_SAFE_DELETE(ret); } return ret; }
- 池管理器
PoolManager
使用一个向量来维护所有的自动释放池。AutoreleasePool* PoolManager::getCurrentPool() const { return _releasePoolStack.back(); } void PoolManager::push(AutoreleasePool *pool) { _releasePoolStack.push_back(pool); } void PoolManager::pop() { CC_ASSERT(!_releasePoolStack.empty()); _releasePoolStack.pop_back(); }
- 自动释放池
AutoreleasePool
也是使用向量_managedObjectArray
来维护指针对象。在clear
方法中会将向量_managedObjectArray
的所有元素交换出来存到临时向量releasings
中(实际上就是想把原向量清空),然后遍历releasings
的所有元素都执行release
操作,即引用计数减一,如果元素的引用计数减到0就会在其relese
方法中被释放掉。void AutoreleasePool::addObject(Ref* object) { _managedObjectArray.push_back(object); } void AutoreleasePool::clear() { #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) _isClearing = true; #endif std::vector<Ref*> releasings; releasings.swap(_managedObjectArray); for (const auto &obj : releasings) { obj->release(); } #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) _isClearing = false; #endif }
Director
会在每一帧的最后调用自动释放池的clear
方法。void Director::mainLoop() { drawScene(); // release the objects PoolManager::getInstance()->getCurrentPool()->clear(); }
总的来说,自动释放池做的工作就是保证在当前帧创建的节点,如果它在这一帧中都没有被引用(被添加为子节点等操作),那么在这一帧最后就会被释放掉。假设当前帧新创建(
create
)的节点在当前帧中被addChild
了,那么它的引用计数变成2(构造函数会将引用计数设为1,并且create
的时候会加入到自动释放池的向量中),那么在这一帧的最后调用自动释放池会将从向量中取出这个节点(向量会被清空),并且调用节点的release
方法,这时候节点的引用计数变成1不会被释放掉。在以后的帧中,自动释放池的向量都不会再持有该节点(除非是主动调用autorelease
方法又把节点加到自动释放池的向量中),所以这个节点就一直存活在场景中,直到它被removeChild
时才会调用release
方法,引用计数减为0从而被释放掉。
- 调用
【参考资料】
[1] Cocos2D-X游戏开发技术精解 - 第10章 内存管理机制
[2] do…while(0)的妙用