I’m out of ideas here, SceneKit is piling on the memory and I’m only getting started. I’m displaying SNCNodes
which are stored in arrays so I can separate compo
I did also experience a lot of memory bloat from SceneKit in my app, with similar memory chunks as you in Instruments (C3DGenericSourceCreateDeserializedDataWithAccessors
, C3DMeshSourceCreateMutable
, etc). I found that setting the geometry
property to nil
on the SCNNode
objects before letting Swift deinitialize them solved it.
In your case, in you cleanup function, do something like:
atomsNode_1.removeFromParentNode()
atomsNode_1.geometry = nil
atomsNode_2.removeFromParentNode()
atomsNode_2.geometry = nil
Another example of how you may implement the cleaning:
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
}
}
If that doesn't work, you may have better success by setting its texture to nil
, as reported on scene kit memory management using swift.
Spheres, capsules, and cylinders all have fairly dense meshes. Do you need all that detail? Try reducing the various segment count properties (segmentCount
, radialSegmentCount
, etc). As a quick test, substitute SCNPyramid
for all of your primitive types (that's the primitive with the lowest vector count). You should see a dramatic reduction in memory use if this is a factor (it will look ugly, but will give you immediate feedback on whether you're on a usable track). Can you use a long SCNBox
instead of a cylinder?
Another optimization step would be to use SCNLevelOfDetail
to allow substitute, low vertex count geometry when an object is far away. That would be more work than simply reducing the segment counts uniformly, but would pay off if you sometimes need greater detail.
Instead of managing the components yourself in arrays, use the node hierarchy to do that. Create each molecule, or animatable piece of a molecule, as a tree of SCNNodes
. Give it a name. Make a flattenedClone
. Now archive that. Read the node tree from archive when you need it; don't worry about arrays of nodes.
Consider writing two programs. One is your iOS program that manipulates/displays the molecules. The other is a Mac (or iOS?) program that generates your molecule node trees and archives them. That will give you a bunch of SCNNode tree archives that you can embed, as resources, in your display program, with no on-the-fly generation.
An answer to scene kit memory management using swift notes the need to nil out "textures" (materials
or firstMaterial
properties?) to release the node. Seems worth a look, although since you're just using UIColor I doubt it's a factor.
Here's an example of creating a compound node and archiving it. In real code you'd separate the archiving from the creation. Note also the use of a long skinny box to simulate a line. Try a chamfer radius of 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
}
}