-
Notifications
You must be signed in to change notification settings - Fork 43
cocos2d x 3.3rc2 003 cocos中的引用计数和自动释放池
David edited this page Jan 14, 2015
·
2 revisions
class CC_DLL Ref
{
public:
/**
* Retains the ownership.
*
* This increases the Ref's reference count.
*/
void retain();
/**
* Releases the ownership immediately.
*
* This decrements the Ref's reference count.
*
* If the reference count reaches 0 after the descrement, this Ref is
* destructed.
*/
void release();
/**
* Releases the ownership sometime soon automatically.
*
* This descrements the Ref's reference count at the end of current
* autorelease pool block.
*
* If the reference count reaches 0 after the descrement, this Ref is
* destructed.
*
* @returns The Ref itself.
*/
Ref* autorelease();
/**
* Returns the Ref's current reference count.
*
* @returns The Ref's reference count.
*/
unsigned int getReferenceCount() const;
protected:
/**
* Constructor
*
* The Ref's reference count is 1 after construction.
*/
Ref();
public:
/**
* deConstructor
*/
virtual ~Ref();
protected:
/// count of references
unsigned int _referenceCount;
};
Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
{
}
Ref::~Ref()
{
}
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)
{
delete this;
}
}
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
unsigned int Ref::getReferenceCount() const
{
return _referenceCount;
}
- 在该类构造的时候_referenceCount = 1
- retain, ++_referenceCount;
- release, --_referenceCount; if (_referenceCount == 0) { delete this; }
- autorelease和下面的自动释放池一起讲解
- c++标准做法是new delete配对,保证堆上对象的释放, 也即谁拥有谁负责。 但当一个堆上的对象被多个其他对象共享时, 谁来最终释放这个共享对象是个麻烦事情, 怎么办呢, 引用计数就是解决方案之一, 它用retain release代替了new delete。 当对象被共享者持有时, 共享者调用对应的retain使计数+1, 共享者用完该共享对象后调用release使计数-1。 这样当最后一个共享者用完并调用release之后, 共享对象的_referenceCount == 0, 共享对象delete自己, 资源正确释放。 引用计数可以免除跟踪对象所有权的担子,因为当使用引用计数后,对象自己拥有自己。当没人再使用它时,它自己自动销毁自己。
- 故我们该怎样用retain release呢? (autorelease放到自动释放之后再讲)
- new分配: x = new xxxRef; 释放: x->release() [这时候可以用c++的标准做法delete x(但前提是知道x没被共享),不推荐]
- 对象从别处获得(共享的对象): 先调用x->retain() ; 释放: x->release() [这时必须先retain了, 否则可能别的拥有者调用了release导致x对应的资源被真的释放, 这里获得的是一个野指针]
- PoolManager
- 单例模式
- friend class AutoreleasePool;
- 数据成员: std::vector<AutoreleasePool*> _releasePoolStack;
- 公开接口: getInstance, destroyInstance, getCurrentPool, isObjectInPools
- 私有函数(对friend class开放): push, pop
- AutoreleasePool
- 数据成员: std::vector<Ref*> _managedObjectArray;
- 公开接口: addObject, clear, contains
- Ref, Ref* Ref::autorelease(){ PoolManager::getInstance()->getCurrentPool()->addObject(this); return this; }
- DisplayLinkDirector, void DisplayLinkDirector::mainLoop(){ /其余代码略/ PoolManager::getInstance()->getCurrentPool()->clear(); }
- ApplicationProtocol, virtual ~ApplicationProtocol(){ PoolManager::destroyInstance(); }
- Node, 成员变量 Vector<Node*> _children;
- Vector, 成员变量std::vector _data; 将引用技术功能和标准库里的vector包装。 注意 AutoreleasePool PoolManager 的成员类型是标准库的类型。
- create一个helloworld layer,并加入场景,其生命周期大致如下(围绕helloworld这个layer)
- app启动,初始化auto release pool
- 各种初始化,包括helloworld layer, hellowrold初始计数器=1,放入autoreleasepool,计数器不变,加入场景后计数器=2
- 开始mainLoop, 每次循环要调用auto release pool的clean,导致对象release,当然helloworld的计数器变为1了。 并清空clear当前autoreleasepool,从这次mainLoop起helloworld layer只被场景拥有。
- 循环循环 mainloop
- 用户请求退出,导演清场,clear所有场景,导致场景的所有child被release一次,故此时helloworld的计数器变为0, 自动释放。
- 系统退出。
- 对比case,我刻意create了另外一个helloworld layer,但不把它通过addChild加入场景中,他的释放时机如下
- app启动,初始化auto release pool
- 各种初始化,包括helloworld layer, hellowrold初始计数器=1,放入autoreleasepool,计数器不变,没有被加入场景计数器仍然为1
- 开始mainLoop, 第一次循环要调用auto release pool的clean,导致对象release,当然helloworld的计数器变为0了,被成功释放
- 通过1 2两个case,可以看出,1. 引用计数才是cocos的基础核心。 2. auto-release-pool仅是为了清除引用计数为1的对象,所谓自动管理,仅限于此。 (或许还有更高级的玩法,待研究TODO)
- 这也能解释https://github.com/chukong/cocos-docs/blob/master/manual/framework/native/v2/memory/refcount-autoreleasepool/zh.md 中提到的一个错误的例子, create了一个对象引用计数=1的数组,被放入auto-release-pool,在mainLoop下一次循环时自然会被release掉。
- 具体流程分析如图,包括启动过程和从计数器角度理解
- 获得对象后用retain,释放对象用release或者autorelease,就这么简单。
- 重复下原因:retain计数器加一; release计数器减一;autorelease将对象加入autoreleasepool,在mainLoop下次会调用autoreleasepool的clear导致对象的release被调用(autorelease相当于间接调用release) 。
- 提一提create函数,cocos的2段构造实现,它作的工作:new一个对象,计数器默认为1;将对象放入了autoreleasepool。 故mainLoop下次循环就会被释放。 要想持有,那就create之后调用retain,并在owner的适当地方如析构函数调用release或autorelease即可。
- 提一提addChild函数,最终调用到Node::insertChild,并最终调用到Vector::pushBack,导致child的retain被调用一次。故常见情况是通过create函数创建obj(引用计数=1),通过addChild来加到相关父容器(引用计数=2)。 (下一次mainLoop,autoreleasepool clean,引用计数变1,对象被父容器拥有) 这一create 一addChild最终导致父容器来拥有obj。
- 理解了这些之后, 实际用起来应该不乱了。
- PoolManager为何有一个类似栈的结构?
- AutoreleasePool为何有这样一个警告@warn Don't create an auto release pool in heap, create it in stack.?
- AutoreleasePool或许还有更高级的玩法?
- 见 cocos2d x 3.3rc2 004 cocos中的引用计数和自动释放池 ReleasePoolTest
Just build something.