Skip to content

cocos2d x 3.3rc2 003 cocos中的引用计数和自动释放池

David edited this page Jan 14, 2015 · 2 revisions

引用计数

3.3rc2里面关于引用技术相关核心类是Ref, 下面是简化后的class Ref

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;
}

Ref代码分析

  • 在该类构造的时候_referenceCount = 1
  • retain, ++_referenceCount;
  • release, --_referenceCount; if (_referenceCount == 0) { delete this; }
  • autorelease和下面的自动释放池一起讲解

原理分析及Ref的用法

  • c++标准做法是new delete配对,保证堆上对象的释放, 也即谁拥有谁负责。 但当一个堆上的对象被多个其他对象共享时, 谁来最终释放这个共享对象是个麻烦事情, 怎么办呢, 引用计数就是解决方案之一, 它用retain release代替了new delete。 当对象被共享者持有时, 共享者调用对应的retain使计数+1, 共享者用完该共享对象后调用release使计数-1。 这样当最后一个共享者用完并调用release之后, 共享对象的_referenceCount == 0, 共享对象delete自己, 资源正确释放。 引用计数可以免除跟踪对象所有权的担子,因为当使用引用计数后,对象自己拥有自己。当没人再使用它时,它自己自动销毁自己。
  • 故我们该怎样用retain release呢? (autorelease放到自动释放之后再讲)
    1. new分配: x = new xxxRef; 释放: x->release() [这时候可以用c++的标准做法delete x(但前提是知道x没被共享),不推荐]
    2. 对象从别处获得(共享的对象): 先调用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 的成员类型是标准库的类型。

原理分析及验证

  1. create一个helloworld layer,并加入场景,其生命周期大致如下(围绕helloworld这个layer)
    1. app启动,初始化auto release pool
    2. 各种初始化,包括helloworld layer, hellowrold初始计数器=1,放入autoreleasepool,计数器不变,加入场景后计数器=2
    3. 开始mainLoop, 每次循环要调用auto release pool的clean,导致对象release,当然helloworld的计数器变为1了。 并清空clear当前autoreleasepool,从这次mainLoop起helloworld layer只被场景拥有。
    4. 循环循环 mainloop
    5. 用户请求退出,导演清场,clear所有场景,导致场景的所有child被release一次,故此时helloworld的计数器变为0, 自动释放。
    6. 系统退出。
  2. 对比case,我刻意create了另外一个helloworld layer,但不把它通过addChild加入场景中,他的释放时机如下
    1. app启动,初始化auto release pool
    2. 各种初始化,包括helloworld layer, hellowrold初始计数器=1,放入autoreleasepool,计数器不变,没有被加入场景计数器仍然为1
    3. 开始mainLoop, 第一次循环要调用auto release pool的clean,导致对象release,当然helloworld的计数器变为0了,被成功释放
  3. 通过1 2两个case,可以看出,1. 引用计数才是cocos的基础核心。 2. auto-release-pool仅是为了清除引用计数为1的对象,所谓自动管理,仅限于此。 (或许还有更高级的玩法,待研究TODO)
    1. 这也能解释https://github.com/chukong/cocos-docs/blob/master/manual/framework/native/v2/memory/refcount-autoreleasepool/zh.md 中提到的一个错误的例子, create了一个对象引用计数=1的数组,被放入auto-release-pool,在mainLoop下一次循环时自然会被release掉。
  4. 具体流程分析如图,包括启动过程和从计数器角度理解 cocos2d-x-3.3rc2-autoreleasepool.jpg

retain release autorelease怎么用?

  • 获得对象后用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。
  • 理解了这些之后, 实际用起来应该不乱了。

疑问 TODO

ref

附录, log for trace auto-release pool

Clone this wiki locally