您的位置:首页 > 移动开发 > Objective-C

CCObject的分析:release retain基于2.2.3 增加3.2ref对比

2016-03-19 10:44 579 查看
CCSprite
* fish =
new
CCSprite;
02.
CCLOG(
"After
new: %d"
,fish->retainCount());
03.
fish->init();
04.
CCLOG(
"After
init: %d"
,fish->retainCount());
05.
fish->retain();
06.
CCLOG(
"After
retain: %d"
,fish->retainCount());
07.
fish->release();
08.
CCLOG(
"After
release: %d"
,fish->retainCount());
09.
fish->autorelease();
10.
CCLOG(
"After
autorelease: %d"
,fish->retainCount());
//实际操作了+1-1.
结果:After new: 1After init: 1After retain: 2After release: 1After autorelease: 1“HelloCpp.exe”(Win32): 已加载“C:WINDOWSSysWOW64SogouTSF.ime”。无法查找或打开
PDB 文件。view
sourceprint?
01.
class
CC_DLL
CCCopying
02.
{
03.
public
:
04.
virtual
CCObject* copyWithZone(CCZone* pZone);
05.
};
06.
07.
/**
08.
*
@js NA
09.
*/
10.
class
CC_DLL
CCObject :
public
CCCopying
11.
{
12.
public
:
13.
//
object id,CCScriptSupport need public m_uID
14.
unsigned
int
m_uID;
15.
//
Lua reference id
16.
int
m_nLuaID;
17.
protected
:
18.
//
count of references
19.
unsigned
int
m_uReference;
20.
//
count of autorelease
21.
unsigned
int
m_uAutoReleaseCount;
22.
public
:
23.
CCObject(
void
);
24.
/**
25.
*
@lua NA
26.
*/
27.
virtual
~CCObject(
void
);
28.
29.
void
release(
void
);
30.
void
retain(
void
);
31.
CCObject*
autorelease(
void
);
32.
CCObject*
copy(
void
);
33.
bool
isSingleReference(
void
)
const
;
34.
unsigned
int
retainCount(
void
)
const
;
35.
virtual
bool isEqual(
const
CCObject*
pObject);
36.
37.
virtual
void
acceptVisitor(CCDataVisitor
&visitor);
38.
39.
virtual
void
update(
float
dt)
{CC_UNUSED_PARAM(dt);};
40.
41.
friend
class
CCAutoreleasePool;
42.
};
如下:默认初始化reference为1,view
sourceprint?
01.
CCObject::CCObject(
void
)
02.
:
m_nLuaID(
0
)
03.
,
m_uReference(
1
)
//
when the object is created,the reference count of it is 1
04.
,
m_uAutoReleaseCount(
0
)
//
05.
{
06.
static
unsigned
int
uObjectCount
=
0
;
07.
08.
m_uID
= ++uObjectCount;
09.
}
看看autorelease的源码就知道实现了+1和-1的操作:view
sourceprint?
1.
CCObject*
CCObject::autorelease(
void
)
2.
{
3.
CCPoolManager::sharedPoolManager()->addObject(
this
);
4.
return
this
;
5.
}
view
sourceprint?
1.
//<span
style="font-family: Arial,Helvetica,sans-serif;">CCAutoreleasePool.cpp</span>
view
sourceprint?
1.
void
CCPoolManager::addObject(CCObject*
pObject)
2.
{
3.
getCurReleasePool()->addObject(pObject);
4.
}
view
sourceprint?
01.
void
CCAutoreleasePool::addObject(CCObject*
pObject)
02.
{
03.
m_pManagedObjectArray->addObject(pObject);
//赫然使用了CCArray。实现了+1的,随着3.2使用C++11语法后使用vector去管理就没有这么纠结了。
04.
05.
06.
CCAssert(pObject->m_uReference
>
1
,
"reference
count should be greater than 1"
);
07.
++(pObject->m_uAutoReleaseCount);
//同时在当前的pool中增加管理
08.
pObject->release();
//
no ref count,in this case autorelease pool added. //必须-1,否则泄露。
09.
}
每个实现单例的类都应该提供清除操作:如下所示,以purge开头。view
sourceprint?
1.
void
CCPoolManager::purgePoolManager()
2.
{
3.
CC_SAFE_DELETE(s_pPoolManager);
4.
}
在2.2系列中,每个CCAutoreleasePool实际上就是一个CCArray,存储了一系列CCObject*,通过m_uAutoReleaseCount来计数。view
sourceprint?
01.
void
CCAutoreleasePool::clear()
02.
{
03.
if
(m_pManagedObjectArray->count()
>
0
)
04.
{
05.
//CCAutoreleasePool*
pReleasePool;
06.
#ifdef
_DEBUG
07.
int
nIndex
= m_pManagedObjectArray->count()-
1
;
08.
#endif
09.
10.
CCObject*
pObj = NULL;
11.
CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray,
pObj)
12.
{
13.
if
(!pObj)
14.
break
;
15.
16.
--(pObj->m_uAutoReleaseCount);
17.
//(*it)->release();
18.
//delete
(*it);
19.
#ifdef
_DEBUG
20.
nIndex--;
21.
#endif
22.
}
23.
24.
m_pManagedObjectArray->removeAllObjects();
25.
}
26.
}
回顾一下CCDiretor的初始化源码段:view
sourceprint?
01.
//
scheduler
02.
m_pScheduler
=
new
CCScheduler();
//
计时器
03.
//
action manager
04.
m_pActionManager
=
new
CCActionManager();
//动作管理器
05.
m_pScheduler->scheduleUpdateForTarget(m_pActionManager,
kCCPrioritySystem,
false
);
06.
//
touchDispatcher
07.
m_pTouchDispatcher
=
new
CCTouchDispatcher();
//触摸信号管理器
08.
m_pTouchDispatcher->init();
09.
10.
//
KeypadDispatcher
11.
m_pKeypadDispatcher
=
new
CCKeypadDispatcher();
//键盘信号管理器
12.
13.
//
Accelerometer
14.
m_pAccelerometer
=
new
CCAccelerometer();
//
加速器管理器
15.
16.
//
create autorelease pool
17.
CCPoolManager::sharedPoolManager()->push();
//创建一个当前的pool并加入PoolManager中,还是一个CCArray,名为:m_pReleasePoolStack,实际上触控更换为vector是从2.0的版本左右就计划好了。2.x版本是个过渡,也就是说,了解了2.x,更具版本更新说明,3.x不存在代沟。
通告层NotificationNode使用说明下面有个NotificationNode,实际上和Scene属于UI种类,因为是后绘制,会遮盖当前scene,用途在于loading或者提示这种弹窗,记得触摸屏蔽的相关设置view
sourceprint?
01.
//
Draw the Scene
02.
void
CCDirector::drawScene(
void
)
03.
{
04.
//
calculate "global" dt
05.
calculateDeltaTime();
06.
07.
//tick
before glClear: issue #533
08.
if
(!
m_bPaused)
09.
{
10.
m_pScheduler->update(m_fDeltaTime);
11.
}
12.
13.
glClear(GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT);
14.
15.
/*
to avoid flickr,nextScene MUST be here: after tick and before draw.
16.
XXX:
Which bug is this one. It seems that it can't be reproduced with v0.9 */
17.
if
(m_pNextScene)
18.
{
19.
setNextScene();
20.
}
21.
22.
kmGLPushMatrix();
23.
24.
//
draw the scene
25.
if
(m_pRunningScene)
26.
{
27.
m_pRunningScene->visit();
28.
}
29.
30.
//
draw the notifications node
31.
if
(m_pNotificationNode)
32.
{
33.
m_pNotificationNode->visit();
34.
}
35.
36.
if
(m_bDisplayStats)
37.
{
38.
showStats();
39.
}
40.
41.
kmGLPopMatrix();
42.
43.
m_uTotalFrames++;
44.
45.
//
swap buffers
46.
if
(m_pob<a
href=
"http://www.it165.net/Pro/pkgame/"
target=
"_blank"
class
=
"keylink"
>OpenGL</a>View)
47.
{
48.
m_pob<a
href=
"http://www.it165.net/Pro/pkgame/"
target=
"_blank"
class
=
"keylink"
>OpenGL</a>View->swapBuffers();
49.
}
50.
51.
if
(m_bDisplayStats)
52.
{
53.
calculateMPF();
54.
}
55.
}
原以为会是在drawscene内调用,谁知不是。这是个题外话,无关紧要的。回归正题:在经过CCDisplayLinkDirector继承CCDirector,在mainLoop中调用poolManager,这个才是真正的cocos程序循环,程序入口的是win32或者ios的主程序循环:简化版:view
sourceprint?
01.
int
CCApplication::run()
02.
{
03.
<span
style=
"white-space:pre"
>
//
Initialize instance and cocos2d.
04.
if
(!applicationDidFinishLaunching())
05.
{
06.
return
0
;
07.
}
08.
09.
10.
CCEGLView*
pMainWnd = CCEGLView::sharedOpenGLView();
11.
pMainWnd->centerWindow();
12.
ShowWindow(pMainWnd->getHWnd(),
SW_SHOW);
13.
14.
15.
while
(
1
)
16.
{
17.
if
(!
PeekMessage(&msg,NULL,
0
,
0
,
PM_REMOVE))
18.
{
19.
//
Get current time tick.
20.
QueryPerformanceCounter(&nNow);
21.
22.
23.
//
If it's the time to draw next frame,draw it,else sleep a while.
24.
if
(nNow.QuadPart
- nLast.QuadPart > m_nAnimationInterval.QuadPart)
25.
{
26.
nLast.QuadPart
= nNow.QuadPart;
27.
CCDirector::sharedDirector()->mainLoop();
28.
}
29.
else
30.
{
31.
Sleep(
0
);
32.
}
33.
continue
;
34.
}
35.
36.
37.
if
(WM_QUIT
== msg.message)
38.
{
39.
//
Quit message loop.
40.
break
;
41.
}
42.
43.
44.
//
Deal with windows message.
45.
if
(!
m_hAccelTable || ! TranslateAccelerator(msg.hwnd,m_hAccelTable,&msg))
46.
{
47.
TranslateMessage(&msg);
48.
DispatchMessage(&msg);
49.
}
50.
}</span>
51.
52.
53.
54.
}
view
sourceprint?
01.
void
CCDisplayLinkDirector::mainLoop(
void
)
02.
{
03.
if
(m_bPurgeDirecotorInNextLoop)
/*虽然cocos2d允许多个director,准备兼容多窗口程序(实际上到目前3.2为止,cocos2d应用多为是单窗口,只有一个窗口句柄,支持多场景、层,支持C++11多线程的引擎
*/
04.
{
//导演类清理自己
05.
m_bPurgeDirecotorInNextLoop
=
false
;
06.
purgeDirector();
07.
}
08.
else
if
(!
m_bInvalid)
09.
{
10.
drawScene();
11.
12.
//
release the objects
13.
CCPoolManager::sharedPoolManager()->pop();
14.
}
15.
}
实际上终于回到了我们要密切关注的CCObject内存回收机制了:每次pop()都去调用当前CCAutoreleasePool的clear操作,因为原理在于Director将游戏世界切片为每一帧,并为每一帧设定绘制时间长度,当GPU和CPU在设备上完成上一帧的制作,会计算一个时间长度,长度不够时会占用当前帧的可用时长,当前帧绘制的必要时长于剩余帧时长时跳过当前帧的CPU执行和GPU绘制,这就是为什么掉帧和碰撞穿透的原因,而在clear操作中,代码如下:view
sourceprint?
01.
void
CCAutoreleasePool::clear()
02.
{
03.
if
(m_pManagedObjectArray->count()
>
0
)
04.
{
05.
//CCAutoreleasePool*
pReleasePool;
06.
#ifdef
_DEBUG
07.
int
nIndex
= m_pManagedObjectArray->count()-
1
;
08.
#endif
09.
10.
CCObject*
pObj = NULL;
11.
CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray,
pObj)
12.
{
13.
if
(!pObj)
14.
break
;
15.
16.
--(pObj->m_uAutoReleaseCount);
17.
//(*it)->release();
18.
//delete
(*it);
19.
#ifdef
_DEBUG
20.
nIndex--;
21.
#endif
22.
}
23.
24.
m_pManagedObjectArray->removeAllObjects();
25.
}
26.
}
将当前帧的数据清理掉,m_uAutoReleaseCount变成0,但是仍未真正释放内存。这时候内存管理机制就依靠m_uReference来辨别是否需要清理。所有只使用addChild而不retain的都会随着父节点被清理掉。一方面我们可以自己pushPool,另一方面可以使用挂载在m_pobScenesStack中当前scene下。poolManager并不管理m_pobScenesStack主线的节点,只管理掉落在当前pool中通过autorelease增加至pool的CCObject。3.2 ref对比待续ref 内部变化:变得清爽多了,除了增加了 CC_USE_MEM_LEAK_DETECTION 预编译模式下的内存跟踪:(这两个不是ref内部函数)static void trackRef(Ref* ref);static void untrackRef(Ref* ref);外,减少了原先CCObject中的各种引用,只剩下:_referenceCount,无论lua还是js、c++,公用一个引用计数,C++分支真的往库或者大型3D多线程发展啦。只要坚持了过渡期,学好python、Linux的相关编译,手游纯3D的可行性很高。下面才是新增的内存追踪显示函数:view
sourceprint?
1.
#
if
CC_USE_MEM_LEAK_DETECTION
2.
public
:
3.
static
void
printLeaks();
4.
#endif
新的AutoreleasePool已经使用:std::vector<Ref*> _managedObjectArray; 标准C++作为容器,通过ref.autorelease()调用的代码中不在进行+-1啦,view
sourceprint?
1.
void
AutoreleasePool::addObject(Ref*
object)
2.
{
3.
_managedObjectArray.push_back(object);
4.
}
同时在mainLopp中变成了直接调用Autorelease的clear()操作:view
sourceprint?
01.
void
DisplayLinkDirector::mainLoop()
02.
{
03.
if
(_purgeDirectorInNextLoop)
04.
{
05.
_purgeDirectorInNextLoop
=
false
;
06.
purgeDirector();
07.
}
08.
else
if
(!
_invalid)
09.
{
10.
drawScene();
11.
12.
//
release the objects
13.
PoolManager::getInstance()->getCurrentPool()->clear();
14.
}
15.
}
3.2中view
sourceprint?
01.
void
Director::drawScene()
02.
{
03.
//
calculate "global" dt
04.
calculateDeltaTime();
05.
06.
//
skip one flame when _deltaTime equal to zero.
07.
if
(_deltaTime
< FLT_EPSILON)
08.
{
09.
return
;
10.
}
11.
12.
if
(_openGLView)
13.
{
14.
_openGLView->pollInputEvents();
15.
}
16.
17.
//tick
before glClear: issue #533
18.
if
(!
_paused)
19.
{
20.
_scheduler->update(_deltaTime);
21.
_eventDispatcher->dispatchEvent(_eventAfterUpdate);
22.
}
23.
24.
glClear(GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT);
25.
26.
/*
to avoid flickr,nextScene MUST be here: after tick and before draw.
27.
XXX:
Which bug is this one. It seems that it can't be reproduced with v0.9 */
28.
if
(_nextScene)
29.
{
30.
setNextScene();
31.
}
32.
33.
pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
34.
35.
//
draw the scene
36.
if
(_runningScene)
37.
{
38.
_runningScene->visit(_renderer,
Mat4::IDENTITY,
false
);
39.
_eventDispatcher->dispatchEvent(_eventAfterVisit);
40.
}
41.
42.
//
draw the notifications node
43.
if
(_notificationNode)
44.
{
45.
_notificationNode->visit(_renderer,
Mat4::IDENTITY,
false
);
46.
}
47.
48.
if
(_displayStats)
49.
{
50.
showStats();
51.
}
52.
53.
_renderer->render();
54.
_eventDispatcher->dispatchEvent(_eventAfterDraw);
55.
56.
popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
57.
58.
_totalFrames++;
59.
60.
//
swap buffers
61.
if
(_openGLView)
62.
{
63.
_openGLView->swapBuffers();
64.
}
65.
66.
if
(_displayStats)
67.
{
68.
calculateMPF();
69.
}
70.
}
通过调用setNextScene();来在每次绘制之前增加scene的引用计数,这样子在drawscene之后调用clear就能清楚了无用的内存ref对象:view
sourceprint?
01.
void
Director::setNextScene()
02.
{
03.
bool
runningIsTransition = dynamic_cast<TransitionScene*>(_runningScene)!= nullptr;
04.
bool
newIsTransition = dynamic_cast<TransitionScene*>(_nextScene)!= nullptr;
05.
06.
//
If it is not a transition,call onExit/cleanup
07.
if
(!
newIsTransition)
08.
{
09.
if
(_runningScene)
10.
{
11.
_runningScene->onExitTransitionDidStart();
12.
_runningScene->onExit();
13.
}
14.
15.
//
issue #709. the root node (scene)should receive the cleanup message too
16.
//
otherwise it might be leaked.
17.
if
(_sendCleanupToScene
&& _runningScene)
18.
{
19.
_runningScene->cleanup();
20.
}
21.
}
22.
23.
if
(_runningScene)
24.
{
25.
_runningScene->release();
26.
}
27.
_runningScene
= _nextScene;
28.
_nextScene->retain();
29.
_nextScene
= nullptr;
30.
31.
if
((!
runningIsTransition)&& _runningScene)
32.
{
33.
_runningScene->onEnter();
34.
_runningScene->onEnterTransitionDidFinish();
35.
}
36.
}
同时看一下Node::addChild的操作,能解决我们的疑惑,使用了addChild和Autorelease两种内存管理的区别:view
sourceprint?
1.
void
Node::addChild(Node*
child,
int
localZOrder,
const
std::string
&name)
2.
{
3.
CCASSERT(child
!= nullptr,
"Argument
must be non-nil"
);
4.
CCASSERT(child->_parent
== nullptr,
"child
already added. It can't be added again"
);
5.
6.
addChildHelper(child,
localZOrder,INVALID_TAG,name,
false
);
7.
}
view
sourceprint?
01.
void
Node::addChildHelper(Node*
child,
int
localZOrder,
int
tag,
const
std::string
&name,bool setTag)
02.
{
03.
if
(_children.empty())
04.
{
05.
this
->childrenAlloc();
06.
}
07.
08.
this
->insertChild(child,
localZOrder);
09.
10.
if
(setTag)
11.
child->setTag(tag);
12.
else
13.
child->setName(name);
14.
15.
child->setParent(
this
);
16.
child->setOrderOfArrival(s_globalOrderOfArrival++);
17.
18.
#
if
CC_USE_PHYSICS
19.
//
Recursive add children with which have physics body.
20.
Scene*
scene =
this
->getScene();
21.
if
(scene
!= nullptr && scene->getPhysicsWorld()!= nullptr)
22.
{
23.
child->updatePhysicsBodyTransform(scene);
24.
scene->addChildToPhysicsWorld(child);
25.
}
26.
#endif
27.
28.
if
(
_running )
29.
{
30.
child->onEnter();
31.
//
prevent onEnterTransitionDidFinish to be called twice when a node is added in onEnter
32.
if
(_isTransitionFinished)
{
33.
child->onEnterTransitionDidFinish();
34.
}
35.
}
36.
37.
if
(_cascadeColorEnabled)
38.
{
39.
updateCascadeColor();
40.
}
41.
42.
if
(_cascadeOpacityEnabled)
43.
{
44.
updateCascadeOpacity();
45.
}
46.
}
这里要注意了,addChild中用到的vector不是std的vector,是CCVector,cocos2d重写的,view
sourceprint?
1.
void
pushBack(T
object)
2.
{
3.
CCASSERT(object
!= nullptr,
"The
object should not be nullptr"
);
4.
_data.push_back(
object );
5.
object->retain();
6.
}
所以实际上我们看似使用AddChild交给cocos2d的渲染树帮助我们管理内存和启用AutoreleasePool形式管理内存是不相干,实际就相当于在以pool为载体的区域内建立了一批对象,根据需要去retian,或者挂载了渲染树上(每棵渲染树就是以scene为起点,addChild执行一次retian),每次渲染结束后都会清理一次内存池。所有在new之后,不调用Autorelease就要我们手动采取C++标准方式去管理内存;如果启用Autorelease的机制,因为调用Autorelease实在drawscene(cpu游戏主线和GPU渲染)之后,没有了之前存在可能申请了马上被清理的可能,所以不需要+-1,只需要在clear的时候判断是否为0。AddChild的区别在于挂载在渲染树上的节点_referenceCount初始为2,在执行一次clear之后就不在属于PoolManager的当中的AutoReleasePool,而是只属于渲染树啦。渲染树的清理:view
sourceprint?
01.
void
Director::replaceScene(Scene
*scene)
02.
{
03.
CCASSERT(_runningScene,
"Use
runWithScene: instead to start the director"
);
04.
CCASSERT(scene
!= nullptr,
"the
scene should not be null"
);
05.
06.
if
(scene
== _nextScene)
07.
return
;
08.
09.
if
(_nextScene)
10.
{
11.
if
(_nextScene->isRunning())
12.
{
13.
_nextScene->onExit();
14.
}
15.
_nextScene->cleanup();
16.
_nextScene
= nullptr;
17.
}
18.
19.
ssize_t
index = _scenesStack.size();
20.
21.
_sendCleanupToScene
=
true
;
22.
_scenesStack.replace(index
-
1
,
scene);
23.
24.
_nextScene
= scene;
25.
}
view
sourceprint?
1.
_nextScene->cleanup();
调用的是Node的cleanup,递归形式完成渲染树的清理。提示: //利用静态函数实现每个pool创建自动添加至Manager,用的真妙!!C++只会越用越熟啊。view
sourceprint?
01.
PoolManager*
PoolManager::getInstance()
02.
{
03.
if
(s_singleInstance
== nullptr)
04.
{
05.
s_singleInstance
=
new
PoolManager();
06.
//
Add the first auto release pool
07.
new
AutoreleasePool(
"cocos2d
autorelease pool"
);
08.
}
09.
return
s_singleInstance;
10.
}
view
sourceprint?
01.
AutoreleasePool::AutoreleasePool(
const
std::string
&name)
02.
:
_name(name)
03.
#
if
defined(COCOS2D_DEBUG)
&& (COCOS2D_DEBUG >
0
)
04.
,
_isClearing(
false
)
05.
#endif
06.
{
07.
_managedObjectArray.reserve(
150
);
08.
PoolManager::getInstance()->push(
this
);
09.
}
所以每次执行完clear,Manager中除了渲染树和_referenceCount>1都会被清理掉;可是pool被清理之后_referenceCount>=1的内存就如渲染树般泄露了,这个时候就需要我用自己release(delete)了。为什么会出现这样子的情况呢,实际上源于早前兼容ObjectC的代码继承过来的管理机制,可能之后Pool和Autorelease的机制会被清理调用,只保存渲染树和手动retian、release。这和之后启用C++11
shared_ptr<typename>有关。因为3.2的pool实际上可有可无啦,完全可以让程序去实现了内存管理。至于ref继承了clone接口,这个人尽皆知,就不分析啦。可惜目前我仍然无法搭建git环境,我最怕搭建环境了,比如花了4天去配置cocos2dx 2.x 和quick的win安卓环境,后来放弃了之后发现3.x完全是方便至极。虚拟机mac环境早搞定了,只剩sdk下载。下面是源码推荐的用法:view
sourceprint?
01.
//
Trigger an assert if the reference count is 0 but the Ref is still in autorelease pool.
02.
//
This happens when 'autorelease/release' were not used in pairs with 'new/retain'.
03.
//
04.
//
Wrong usage (1):
05.
//
06.
//
auto obj = Node::create();   // Ref = 1,but it's an autorelease Ref which means it was in the autorelease pool.
07.
//
obj->autorelease(); // Wrong: If you wish to invoke autorelease several times,you should retain `obj` first.
08.
//
09.
//
Wrong usage (2):
10.
//
11.
//
auto obj = Node::create();
12.
//
obj->release(); // Wrong: obj is an autorelease Ref,it will be released when clearing current pool.
13.
//
14.
//
Correct usage (1):
15.
//
16.
//
auto obj = Node::create();
17.
//
|-   new Node();     // `new` is the pair of the `autorelease` of next line
18.
//
|-   autorelease();  // The pair of `new Node`.
19.
//
20.
//
obj->retain();
21.
//
obj->autorelease();  // This `autorelease` is the pair of `retain` of previous line.
22.
//
23.
//
Correct usage (2):
24.
//
25.
//
auto obj = Node::create();
26.
//
obj->retain();
27.
//
obj->release();   // This `release` is the pair of `retain` of previous line.

FROM: http://www.it165.net/pro/html/201410/23249.html 
                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: