我在这里没有想法,SceneKit正在堆积内存,我才开始。我正在显示存储在数组中的SNCNodes
,因此我可以分离分子的组件以进行动画处理。这些树模型分子我最终可能有50个显示,比如每个“章节”一个。问题是,当我转到另一章时,前面章节中的分子仍然存在于记忆中。
分子节点是子节点的树。大约一半的节点是空容器,用于定位。否则,几何形状为SCNPrimitives
(球体,胶囊和圆柱体)。每个几何体都有一个镜面和一个由material
组成的漫反射UIColor
,没有使用纹理。
当应用程序首次启动时,这些分子由代码构建并存档到字典中。然后,在随后的引导中,存档的字典被读入本地字典以供VC使用。 (为简洁起见,我在本文中删除了安全功能。)
moleculeDictionary = Molecules.readFile() as! [String: [SCNNode]]
当章节想要显示分子时,它会调用一个特定的函数,将给定分子所需的组分从本地字典加载到本地SCNNode
属性中。
// node stores (reuseable)
var atomsNode_1 = SCNNode()
var atomsNode_2 = SCNNode()
. . .
func lysozyme() { // called by a chapter to display this molecule
. . .
components = moleculeDictionary["lysozyme"]
atomsNode_1 = components[0] // protein w/CPK color
baseNode.addChildNode(atomsNode_1)
atomsNode_2 = components[2] // NAG
baseNode.addChildNode(atomsNode_2)
. . .
}
在显示下一个分子之前,我称之为“清理”功能:
atomsNode_1.removeFromParentNode()
atomsNode_2.removeFromParentNode()
. . .
当我在乐器中进行调查时,大多数臃肿的内存是由C3DMeshCreateFromProfile
调用的32 kB块和C3DMeshCreateCopyWithInterleavedSources
的80 kB块。
我还需要跟踪哪些漏洞可追溯到归档的NSKeyedUnarchiver
解码。所以我也需要处理这些,但它们只是累积每个分子调用的内存使用量的一小部分。
如果我回到之前查看的分子,内存使用量没有进一步增加,它会累积并持续存在。
我尝试声明atomsNode_1
及其亲属作为选项,然后在清理时将它们设置为零。没有帮助。我已经尝试过,在清理功能中,
atomsNode_1.enumerateChildNodesUsingBlock({
node, stop in
node.removeFromParentNode()
})
好吧,内存会回落,但节点似乎永远不会从加载的字典中消失。该死的参考类型!
所以我可能需要一种方法来存档[SCNNode]
数组,以便单独解压缩和检索它们。在这种情况下,我会在完成后清除它们的内存,并在重新访问该分子时从存档重新加载。但我知道还没怎么做这些。在投入更多时间感到沮丧之前,我对此表示赞赏。
答案 0 :(得分:4)
球体,胶囊和圆柱都有相当密集的网格。你需要所有细节吗?尝试减少各种细分计数属性(segmentCount
,radialSegmentCount
等)。作为快速测试,将SCNPyramid
替换为所有基本类型(即具有最低向量计数的基元)。如果这是一个因素,你应该会看到内存使用的显着减少(它看起来很难看,但会立即给你关于你是否在可用轨道上的反馈)。你可以使用长SCNBox
而不是圆柱吗?
另一个优化步骤是使用SCNLevelOfDetail
来允许当物体远离时替代的低顶点计数几何体。这比简单地统一减少分段计数要多得多,但如果你有时需要更多的细节,那将会有所回报。
不是自己在数组中管理组件,而是使用节点层次结构来执行此操作。创建每个分子,或分子的可动画片段,作为SCNNodes
的树。给它起个名字。制作一个flattenedClone
。现在存档。需要时从存档中读取节点树;不要担心节点数组。
考虑编写两个程序。一个是操纵/显示分子的iOS程序。另一个是Mac(或iOS?)程序,它可以生成分子节点树并将其归档。这将为您提供一组SCNNode树存档,您可以将它们作为资源嵌入到显示程序中,而不需要动态生成。
对scene kit memory management using swift的回答指出需要排除“纹理”(materials
或firstMaterial
属性?)以释放节点。看起来值得一看,虽然因为你刚刚使用UIColor,我怀疑它是一个因素。
以下是创建复合节点并将其归档的示例。在实际代码中,您将归档与创建分开。还要注意使用长瘦框来模拟一条线。尝试倒角半径为0!
extension SCNNode {
public class func gizmoNode(axisLength: CGFloat) -> SCNNode {
let offset = CGFloat(axisLength/2.0)
let axisSide = CGFloat(0.1)
let chamferRadius = CGFloat(axisSide)
let xBox = SCNBox(width: axisLength, height: axisSide, length: axisSide, chamferRadius: chamferRadius)
xBox.firstMaterial?.diffuse.contents = NSColor.redColor()
let yBox = SCNBox(width: axisSide, height: axisLength, length: axisSide, chamferRadius: chamferRadius)
yBox.firstMaterial?.diffuse.contents = NSColor.greenColor()
let zBox = SCNBox(width: axisSide, height: axisSide, length: axisLength, chamferRadius: chamferRadius)
zBox.firstMaterial?.diffuse.contents = NSColor.blueColor()
let xNode = SCNNode(geometry: xBox)
xNode.name = "X axis"
let yNode = SCNNode(geometry: yBox)
yNode.name = "Y axis"
let zNode = SCNNode(geometry: zBox)
zNode.name = "Z axis"
let result = SCNNode()
result.name = "Gizmo"
result.addChildNode(xNode)
result.addChildNode(yNode)
result.addChildNode(zNode)
xNode.position.x = offset
yNode.position.y = offset
zNode.position.z = offset
let data = NSKeyedArchiver.archivedDataWithRootObject(result)
let filename = "gizmo"
// Save data to file
let DocumentDirURL = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
// made the extension "plist" so you can easily inspect it by opening in Finder. Could just as well be "scn" or "node"
// ".scn" can be opened in the Xcode Scene Editor
let fileURL = DocumentDirURL.URLByAppendingPathComponent(filename).URLByAppendingPathExtension("plist")
print("FilePath:", fileURL.path)
if (!data.writeToURL(fileURL, atomically: true)) {
print("oops")
}
return result
}
}
答案 1 :(得分:1)
我在我的应用程序中也经历了很多来自SceneKit的内存膨胀,其中的内存块与您在Instruments(C3DGenericSourceCreateDeserializedDataWithAccessors
,C3DMeshSourceCreateMutable
等中的内存块相似。我发现在允许Swift取消初始化它们之前,在geometry
对象上将nil
属性设置为SCNNode
。
在您的情况下,在您的清理功能中,执行以下操作:
atomsNode_1.removeFromParentNode()
atomsNode_1.geometry = nil
atomsNode_2.removeFromParentNode()
atomsNode_2.geometry = nil
另一个如何实施清洁的例子:
class ViewController: UIViewController {
@IBOutlet weak var sceneView: SCNView!
var scene: SCNScene!
// ...
override func viewDidLoad() {
super.viewDidLoad()
scene = SCNScene()
sceneView.scene = scene
// ...
}
deinit {
scene.rootNode.cleanup()
}
// ...
}
extension SCNNode {
func cleanup() {
for child in childNodes {
child.cleanup()
}
geometry = nil
}
}
如果这不起作用,可以通过将其纹理设置为nil
来获得更好的成功,如scene kit memory management using swift所述。