记一次 Cocos2dx-Lua 闪退
闪退点
Version: Cocos2dx-Lua 3.16
在某个页面修改后,出现闪退。查看修改记录没有特别的地方,均为修改图片或者条件判断。
启动 XCode 发现闪退位置出现在void Node::onEnterTransitionDidFinish()
中,
在其调用子节点的child->onEnterTransitionDidFinish()
时,child
指针无效。
|
|
此时的调用栈:
0: void Node::onEnterTransitionDidFinish() -> child->onEnterTransitionDidFinish();
1: void Director::setNextScene() -> _runningScene->onEnterTransitionDidFinish();
2: void Director::drawScene() -> setNextScene();
...
对应 Lua 代码流程:
display.runScene(HomeScene)
HomeScene:ctor() {
self:addChild(HomeLayer)
}
怀疑
因为是在调用Scene::onEnterTransitionDidFinish()
时指针无效,所以猜测是遍历过程中字节点被删除。
查看相关代码:
HomeScene: 没有 onEnterTransitionDidFinish()
HomeLayer: 有 onEnterTransitionDidFinish(),
但在 onEnterTransitionDidFinish 中没有删除 HomeScene 上的结点。
而是往 Scene 上新增结点 addChild(GuideLayer)
因为游戏逻辑是只创建一个GuideLayer
,重复创建时会删除上次创建的页面。又怀疑是否创建了两次GuideLayer
。
通过屏蔽代码发现不添加GuideLayer
就不会闪退,但是GuideLayer
的确只创建了一次,不存在删除问题。
真相
再次分析流程
HomeScene:onEnterTransitionDidFinish():
HomeLayer:onEnterTransitionDidFinish(
addChild(GuideLayer) // 添加到HomeScene上
)
HomeScene:onEnterTransitionDidFinish() 闪退
总归问题是出现在HomeScene
的_children
容器上,那么既然不是删除,增加结点会导致容器出问题吗?
查找代码得到_children
类型为CCVector
,而CCVector
通过std::vector
实现。
所以出现了常见的**_问题:在容器迭代过程中删除或插入_**。
在新增结点往std::vector
插入的时候,恰好超过容量大小,导致扩容。而std::vector
是分配的连续内存,
所以扩容时候会重新分配内存,导致遍历的时候出现child
指针无效。
scene::onEnterTransitionDidFinish() {
for (child: scene->children) {
// onEnterTransitionDidFinish里面往scene中addChild
// 导致children容器扩容,for循环的child指针失效
// 然后下一轮循环再到这里,child(无效指针)->onEnterTransitionDidFinish()就会闪退
child->onEnterTransitionDidFinish();
}
}
else
那么是不是就不能在onEnterTransitionDidFinish
中添加结点呢?
在这个项目,许多页面都在onEnterTransitionDidFinish
往HomeScene
添加GuideLayer
;或者在当前页面添加结点,为啥运行了这么久都没发现问题?
查看代码可以得到,C++
中onEnterTransitionDidFinish
是在遍历完子节点后,
再发送消息调用Lua
中的onEnterTransitionDidFinish
的:
for( const auto &child: _children)
child->onEnterTransitionDidFinish();
...
// 调用当前结点 Lua 的 onEnterTransitionDidFinish
ScriptEngineManager::sendNodeEventToLua(this, kNodeOnEnterTransitionDidFinish);
而项目中的页面管理是这样做的:进入游戏创建HomeScene
,之后其他页面跳转均在HomeScene
上删除和添加,几乎不会创建新 Scene 。
所以后面不会再遇到HomeScene::onEnterTransitionDidFinish
遍历时添加子结点的情况;而直接往当前页面添加结点,也是在当前页面的_children
遍历完后再添加的(Lua 中添加)。