Assert failed: Node still marked as running on node destruction! Was base class onExit() called in derived class onExit() implementations?
在写代码时报了这个错误,主要做的事是监听一个node,在其移除的时候调用另一个node的removeFromParent,实现关联移除。
网上去查,普遍是说原因是一个node重载了Node类的onExit方法却没调用父类onExit方法,解决方法为删掉自己写的onExit或者在其中调用Node类的onExit方法。
因为自己用的是lua,水平也有限,不会重载更不会调用Node类的onExit,觉得似乎问题不在这里。。。但网上找的解决方法几乎都是如此。。。
于是乎,尝试第一次查看cocos源码:
首先,这个是Node类的析构函数里的一句:
Node::~Node()
{
//...sth
CCASSERT(!_running, "Node still marked as running on node destruction! Was base class onExit() called in derived class onExit() implementations?");
}
然后我们去找这个_running在哪里被赋值:
void Node::onEnter()
{
//...sth
_running = true;
}
...
void Node::onExit()
{
//...sth
_running = false;
}
可以看得出来,node在生成的时候调用onEnter此时_running = true, 而退出的时候调用onExit此时_running = false。
貌似还没找到关键,查看报错,发现是removeFromParent的时候报的错,从这里看起:
void Node::removeFromParent()
{
this->removeFromParentAndCleanup();
}
void Node::removeFromParentAndCleanup(bool cleanup)
{
if(_parent != nullptr)
{
_parent->removeChild(this, cleanup)
}
}
看得出来,子节点移除也就是用父节点执行removeChild罢了。继续看下去:
void Node::removeChild(Node* child, bool cleanup /* = true */)
{
// explicit nil handling
if (_children.empty())
{
return;
}
ssize_t index = _children.getIndex(child);
if( index != CC_INVALID_INDEX )
this->detachChild( child, index, cleanup );
}
接着detachChild:
void Node::detachChild(Node *child, ssize_t childIndex, bool doCleanup)
{
// IMPORTANT:
// -1st do onExit
// -2nd cleanup
if (_running)
{
child->onExitTransitionDidStart();
child->onExit();
}
//...sth
// set parent nil at the end
child->setParent(nullptr);
_children.erase(childIndex);
}
这里发现一个节点在移除子节点的时候会先调用子节点的onExit方法进行移除前的一些操作,嗯,跟我们想的差不多。等下~!这里发现要调用子节点的oneExit是有条件的:if (_running)
又是_running…
也就是说,我们的报错很可能是没有进入这个判断之中,没有调用被移除节点的onExit导致的!
而这个条件没进去,代表父节点的_running = false。结合上面的发现,说明父节点已经调用了onExit,这里不允许父节点在onExit之后还进行removeChild操作。
嗯,条理已经很清晰了。现在回想下我代码的逻辑:
local nodeParent = cc.Node.create()
local nodeChild1 = cc.Node.create()
local nodeChild2 = cc.Node.create()
nodeParent:addChild(nodeChild1)
nodeParent:addChild(nodeChild2)
nodeChild1:registerScripterHandler(function(event)
if event == "exit" then
nodeChild2:removeFromParent()
end
end)
差不多是这样子,只是实际上我的”nodeParent”是一个scene,而查看代码发现scene其实也是一个Node。
上述代码会在nodeParent移除的时候报错,报错位置在nodeChild2:removeFromParent()。
根据上述分析,原因显而易见了,child1根child2拥有同一个祖先nodeParent。而要实现的逻辑是:child1移除的时候自动移除child2。但child1移除的情况多是因为parent关闭了,级联受到移除,而此时parent已经调用了自己的onExit的,也就是_running = fasle, 这个时候child2受到child1的级联移除(自己写的逻辑),要调用parent的detachChild,而其中这条语句:
if (_running)
{
child->onExitTransitionDidStart();
child->onExit();
}
此时parent的_running = false, 是进不去这个判断语句的,也就无法调用child2的onExit方法,也就导致了child2在析构时报错~!!
真相大白!可是知道了原因之后,我还是要写这个逻辑啊!怎么办呢?查了一下cocos的api,发现Node的一个方法可以查询一个节点的_running状态。我们只需要这样做:
nodeChild1:registerScripterHandler(function(event)
if event == "exit" then
if nodeChild2:getParent():isRunning() == true then
nodeChild2:removeFromParent()
else
print("nodeChild2的父节点已处于停止运行状态,nodeChild2接下来会被自动移除,不需要重复移除!")
end
end
end)
因为可以看得出,parent已经正在移除状态中,因此这里可以跳过child2的移除,这样后面child2也会被parent级联移除~~~
另外,源码查看方面受这篇文章启发~cocos2dx 3.x内存管理源代码追踪