上一节讲CCActionInterval,这节讲CCActionInstant。同样为了方便,我们用CCPlace来讲解。作为了解过CCActionInterval的过来人来说,CCActionInstant意外的简单。还记得这两种Aciton之间的区别吗?前者需要一个duration,而后者调用一次就介绍了,自然也不用什么持续时间了。翻看源码,可以发现CCActionInstant和CCActionInterval是一套逻辑,毕竟他们都是CCActionInstant的子类。那么可以说所有的CCActionInstant都是这套逻辑了。下面是CCPlace的全部实现:
CCPlace* CCPlace::create(const CCPoint& pos)
{
CCPlace *pRet = new CCPlace();
if (pRet && pRet->initWithPosition(pos)) {
pRet->autorelease();
return pRet;
}
CC_SAFE_DELETE(pRet);
return NULL;
}
bool CCPlace::initWithPosition(const CCPoint& pos) {
m_tPosition = pos;
return true;
}
CCObject * CCPlace::copyWithZone(CCZone *pZone) {
CCZone *pNewZone = NULL;
CCPlace *pRet = NULL;
if (pZone && pZone->m_pCopyObject) {
pRet = (CCPlace*) (pZone->m_pCopyObject);
} else {
pRet = new CCPlace();
pZone = pNewZone = new CCZone(pRet);
}
CCActionInstant::copyWithZone(pZone);
pRet->initWithPosition(m_tPosition);
CC_SAFE_DELETE(pNewZone);
return pRet;
}
void CCPlace::update(float time) {
CC_UNUSED_PARAM(time);
m_pTarget->setPosition(m_tPosition);
}
而在CCAction之中isDone被默认实现为:
bool CCAction::isDone()
{
return true;
}
所以它的逻辑就是调用一次就删,就这么简单。这节有点短,我们再抓几个例子充数吧。比如说CCCallFunc。我之所以抓它来讲,是因为这个action很危险。而且他也有特殊的地方,比如说它的init函数:
bool CCCallFunc::initWithTarget(CCObject* pSelectorTarget) {
if (pSelectorTarget)
{
pSelectorTarget->retain();
}
if (m_pSelectorTarget)
{
m_pSelectorTarget->release();
}
m_pSelectorTarget = pSelectorTarget;
return true;
}
看吧,他把Target retain了一次。init函数还避免掉了重复调用可能出现的bug。这个小特殊没有什么大不了,其危险之处在于采用函数指针来实现回调。
CCObject* m_pSelectorTarget;
intm_nScriptHandler;
union
{
SEL_CallFunc m_pCallFunc;
SEL_CallFuncN m_pCallFuncN;
SEL_CallFuncND m_pCallFuncND;
SEL_CallFuncO m_pCallFuncO;
};
C++被设计为尽早在编译期发现错误,如果你使用函数指针,编译器不太容易发现你的误用。而且一旦误用,就会发生未定义的行为,运行不一定马上崩溃,这种bug是最要命的。好在最新的代码中已经使用function来实现回调。这种实现一定要引以为戒。
还有一个特殊的CCActionInstant action是CCRemoveSelf。这种Action是很方便的,但是我对它的安全性保持怀疑。它的作用是调用一次target的release,而且在init的时候没有将target retain,这有点小hack了。当然整个action的设计都不咋滴。我在前面忘记讲removeFromParentAndCleanup这个函数了,乘此机会来补一下。
void CCNode::removeFromParentAndCleanup(bool cleanup)
{
if (m_pParent != NULL)
{
m_pParent->removeChild(this,cleanup);
}
}
void CCNode::removeChild(CCNode* child,bool cleanup)
{
// explicit nil handling
if (m_pChildren==NULL)
{
return;
}
if(m_pChildren->containsObject(child))
{
this->detachChild(child,cleanup);
}
}
void CCNode::detachChild(CCNode *child, bool doCleanup)
{
// IMPORTANT:
// -1st do onExit
// -2nd cleanup
if (m_bRunning)
{
child->onExitTransitionDidStart();
child->onExit();
}
// If you don't do cleanup, the child's actions will
// not get removed and the
// its scheduledSelectors_ dict will not get released!
if (doCleanup)
{
child->cleanup();
}
// set parent nil at the end
child->setParent(NULL);
m_pChildren->removeObject(child);
}
这个函数本身没什么,如果参数为true则调用cleanup函数,而且默认是为true的。
void CCNode::cleanup()
{
// actions
this->stopAllActions();
this->unscheduleAllSelectors();
if(m_eScriptType!=kScriptTypeNone)
{
CCScriptEngineManager::sharedManager()->
getScriptEngine()->executeNodeEvent(this,kCCNodeOnCleanup);
}
// timers
arrayMakeObjectsPerformSelector(m_pChildren,cleanup,CCNode*);
}
从Parent中移去大体上是这个流程,先调用自己的onExitTransitionDidStart,导致所有的子Node的
onExitTransitionDidStart都调用一遍,调用onExit,导致所有子Node的onExit都调用一遍,调用自己cleanup,导致所有的子Node的cleanup都调用一遍。最后从父容器中移去,也就是引用计数减1.如果引用计数到0,导致自己析构函数调用。这时他会删除自己Children容器,导致所有的子Node的引用计数都减1.
我们知道能对action有影响的是只有cleanup函数,如果在step中调用到了cleanup这个函数,那么就相当于在action中调用了removeAllActionsFromTarget这个函数。前面讲了一个removeActionAtIndex。说这个函数在step中调用是安全的。而且分析了这个函数是最终一个action的step的情况,也是安全的。我们现在分析一下这个函数,算作温习:
void CCActionManager::removeAllActionsFromTarget(CCObject* pTarget)
{
// explicit null handling
if(pTarget==NULL)
{
return;
}
tHashElement* pElement=NULL;
HASH_FIND_INT(m_pTargets,&pTarget,pElement);
if(pElement)
{
if(ccArrayContainsObject(pElement->actions,
pElement->currentAction)&&(!pElement->currentActionSalvaged))
{
pElement->currentAction->retain();
pElement->currentActionSalvaged=true;
}
ccArrayRemoveAllObjects(pElement->actions);
if(m_pCurrentTarget==pElement)
{
m_bCurrentTargetSalvaged=true;
}
else
{
deleteHashElement(pElement);
}
}
else
{
// CCLOG("cocos2d: removeAllActionsFromTarget: Target not found");
}
}
之前说过在step中删除其他节点是安全的,那么有问题的只可能是ccArrayRemoveAllObjects(pElement->actions);这里的情况有2个,如果这个action是最后一个action。循环不再,不会有问题。如果这个action不是最后一个action。之前那个函数不会有问题,是因为它无耻的手动回减了一次index。这里调用了一下ccArrayRemoveAllObjects(pElement->actions),将actions的num减为0,那么在下一次循环判断为false,不再循环也就没什么问题了。