Reference Count and AutoReleasePool in Cocos2d-x¶
Reference Count¶
Reference count is an old approach of memory management in C/C++ projects. When I dived to TCPMP open source project 8 years ago, reference count was there.
iOS SDK wrapped this mechanism in NSAutoreleasePool. So we have a cloned one CCAutoreleasePool in cocos2d-x. The usage is almost the same, so if you haven't developed on iOS, please read Apple's official document first NSAutoreleasePool Class Reference
CCAutoreleasePool¶
CCAutoreleasePool for cocos2d-x has the same concept and same APIs with cocoa NSAutoreleasePool, but with 2 significant differences:- CCAutoreleasePool can not be NESTED. So there's only one pool in each cocos2d-x game instance, game developers CAN NOT new any more CCAutoreleasePool objects, but only focus on release/retain the children of cocos2d::CCObject.
- CCAutoreleasePool cannot be used in multi-thread. So if your game needs a network thread, please just receive the data and change state flags in the network thread, don't call cocos2d interfaces in it. The reason is explained below.
The logic of CCAutoreleasePool is that, when you call object->autorelease(), this object is put into the autorelease pool. The pool can help you to retain this object's lifecycle till the end of current message loop. At the end of current message loop, if this object hasn't been retained by any other class/container, it will be released automaticaly. For example, layer->addChild(sprite), the sprite is added to the 'children list' of the layer, its lifecycle is retained till the release of layer, but not the end of current message loop.
That's why you can not manage CCObjects life cycle in network thread: at each end of UI thread, autorelease object will be deleted, and you will meet with a crash when calling these deleted pointers.
CCObject::release(), retain() and autorelease()¶
In one word, there are only 2 situations you need to call ->release() method
1. You "new" an object of cocos2d::CCObject children, such as CCSprite, CCLayer, etc.
2. You get a pointer of cocos2d::CCObject children, then called "retain" once in your code
An example that no need to call retain and release:
1CCSprite* sprite = CCSprite::create("player.png");
And there's no more code using sprite. Please notice that
stripe->autorelease() has been invoked in CCSprite::create(const char*) method, so the sprite is released automatically at the end of this message loop.
Using Static Constructor¶
CCSprite::create("player.png") is a sample of using static constructor. All classes in cocos2d-x, besides singleton, offer static constructors, which involve 4 operations in it:
- new an object
- call object->init(...)
- if init success, e.g. find the texture file successfully, it will call object->autorelease();
- return the object marked as autorelease.
All CCAsdf::createWithXxxx(...) style functions have the same behavior.
In cocos2d-x v1.x and lower version, this behaviour was:
1CCSprite* sprite = CCSprite::spriteWithTexture(...)
Using these static constructor, you don't need to care about "new", "delete" and "autorelease", just concern the pair of object->retain() and object->release()
An Error Sample¶
A developer reported a crash when using CCArray
1bool HelloWorld::init()
2{
3 bool bRet = false;
4 do
5 {
6 //////////////////////////////////////////////////////////////////////////
7 // super init first
8 //////////////////////////////////////////////////////////////////////////
9
10 CC_BREAK_IF(! CCLayer::init());
11
12 //////////////////////////////////////////////////////////////////////////
13 // add your codes below...
14 //////////////////////////////////////////////////////////////////////////
15
16 CCSprite* bomb1 = CCSprite::create("CloseNormal.png");
17 CCSprite* bomb2 = CCSprite::create("CloseNormal.png");
18 CCSprite* bomb3 = CCSprite::create("CloseNormal.png");
19 CCSprite* bomb4 = CCSprite::create("CloseNormal.png");
20 CCSprite* bomb5 = CCSprite::create("CloseNormal.png");
21 CCSprite* bomb6 = CCSprite::create("CloseNormal.png");
22
23 addChild(bomb1,1);
24 addChild(bomb2,1);
25 addChild(bomb3,1);
26 addChild(bomb4,1);
27 addChild(bomb5,1);
28 addChild(bomb6,1);
29
30 m_pBombsDisplayed = CCArray::create(bomb1,bomb2,bomb3,bomb4,bomb5,bomb6,NULL);
31 //m_pBombsDisplayed is defined in the header as a protected var.
32 // <--- We should add m_pBombsDisplayed->retain() here to avoid crashing in HelloWorld::refreshData()
33
34 this->scheduleUpdate();
35
36 bRet = true;
37 } while (0);
38
39 return bRet;
40}
41
42void HelloWorld::update(ccTime dt)
43{
44 refreshData();
45}
46
47void HelloWorld::refreshData()
48{
49 m_pBombsDisplayed->objectAtIndex(0)->setPosition(cpp(100,100));
50}
His mistake is that, m_pBombsDisplayed is created by CCArray::create(...), in create static constructor, this array is marked as autorelease.
So it's deleted by CCAutoreleasePool at the end of current message loop.
While the subsequent message loop calls HelloWorld::update(ccTime), m_pBombsDisplayed is already a null pointer, which cause the crash.
To resolve the crash, we should add m_pBombsDisplayed->retain() after m_pBombsDisplayed =CCArray::create(...);, and m_pBombsDisplayed->release() in HelloWorld::~HelloWorld() destructor.