复制的SCNGeometry无法在当前RunLoop立即生效问题咨询
我遇到一个问题:复制得到的SCNGeometry无法立即生效,必须通过DispatchQueue.main.async延迟一个RunLoop周期后,复制的模型才能正常显示。这是系统Bug还是我的代码问题?
以下是我的代码:
func helper_cloneGeo(geo: SCNGeometry) -> SCNGeometry { let copiedGeo = geo.copy() as! SCNGeometry copiedGeo.materials = geo.materials.map { $0.copy() as! SCNMaterial } return copiedGeo } override init() { super.init() let geo = readGeo(fn: "alcohol.usdz", texture: "texture.png").geometry! // 这里有问题! // 只有用async包裹后,SCNGeometry才能正常复制显示 // DispatchQueue.main.async { let copiedGeo = self.helper_cloneGeo(geo: geo) let newNode = SCNNode(geometry: copiedGeo) self.rootNode.addChildNode(newNode) // } }
注:readGeo是读取3D模型文件的辅助函数,原模型节点可正常显示,但取消DispatchQueue.main.async包裹后,复制的模型节点无法显示,可通过示例项目复现该问题。
问题分析与解决方案
这种情况大概率不是你的代码问题,而是SceneKit在模型加载阶段的内部状态未同步导致的。
原因解释
当你通过readGeo加载USDZ模型时,SceneKit可能在后台异步处理模型的资源(比如纹理、几何体数据)——即使返回了SCNGeometry对象,其内部的某些初始化操作可能还未完成。此时直接调用copy(),复制得到的几何体可能缺失必要的渲染数据,导致无法显示。而DispatchQueue.main.async会将复制操作推迟到当前RunLoop结束后执行,此时模型的资源加载和内部初始化已完成,复制的几何体就能正常渲染。
更可靠的解决方案
确保模型完全加载后再复制
如果readGeo是自定义函数,建议在模型加载完成的回调中再执行复制操作。如果使用系统的SCNSceneSource加载模型,可以开启异步加载并在完成回调中处理:guard let modelURL = Bundle.main.url(forResource: "alcohol", withExtension: "usdz") else { return } let source = SCNSceneSource(url: modelURL, options: [SCNSceneSource.LoadingOption.asynchronous: true]) source?.loadScene(options: nil, completionHandler: { scene, error in guard let geo = scene?.rootNode.geometry else { return } let copiedGeo = self.helper_cloneGeo(geo: geo) let newNode = SCNNode(geometry: copiedGeo) self.rootNode.addChildNode(newNode) })改用节点克隆替代几何体复制
直接克隆整个SCNNode比单独复制几何体更稳定,因为节点会自动同步所有相关的几何体和材质状态:let originalNode = readGeo(fn: "alcohol.usdz", texture: "texture.png") let clonedNode = originalNode.clone() self.rootNode.addChildNode(clonedNode)手动触发几何体更新
如果必须复制几何体,可以尝试在复制后调用copiedGeo.reload()(部分场景下可强制刷新几何体数据),确保添加到正确的场景层级:let copiedGeo = self.helper_cloneGeo(geo: geo) copiedGeo.reload() let newNode = SCNNode(geometry: copiedGeo) self.rootNode.addChildNode(newNode)
总结
这种延迟生效的现象是SceneKit加载模型时的异步状态未同步导致的,并非你的代码逻辑错误。优先推荐使用节点克隆或等待模型完全加载后再复制几何体,能避免依赖DispatchQueue.main.async的临时解决方案。
内容的提问来源于stack exchange,提问作者OMGPOP

