Swift playgrounds can be ridiculously slow, especially when you’re working with items sourced from SpriteKit and SceneKit. Moving any and all code you can into an external Sources file enables optimized compilation and far better runtime speed.
The big problem with this approach is that you lose the linear flow that you have in playgrounds. Module compilation does not support top level directives that modify variables. So, for example, if you have code that looks like this:
public let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = SCNLightTypeOmni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scnscene.rootNode.addChildNode(lightNode)
(I apologize for the forced unwrap. This was taken from Apple sample code.)
You cannot move these lines directly to a module file because you’re using top-level calls, which will not compile in an external swift file. As a workaround, you might throw most of that into a function, like this:
internal func setupLightNode() -> SCNNode {
let theLight = SCNNode()
theLight.light = SCNLight()
theLight.light!.type = SCNLightTypeOmni
theLight.position = SCNVector3(x: 0, y: 10, z: 10)
scnscene.rootNode.addChildNode(theLight)
return theLight
}
And then call it from your declaration:
let lightNode = setupLightNode()
That’s ugly. It creates an unnecessary extra function and it forces you to place the code that processes a new instance before the instance itself is declared.
Closures offer a (slightly) more appealing solution, as you see in the following example. Here, the initialization lines are wrapped together into a single group, and executed, returning the fully initialized instance at the end.
// create and add a light to the scene
public let lightNode: SCNNode = {
let theLight = SCNNode()
theLight.light = SCNLight()
theLight.light!.type = SCNLightTypeOmni
theLight.position = SCNVector3(x: 0, y: 10, z: 10)
scnscene.rootNode.addChildNode(theLight)
return theLight
}()
This approach enables you to build and initialize an item using a single statement, retain the flow and readability of your playground declarations, but run things quite a bit faster.
Here’s a video of this lightNode code in action, running along with various other setup tasks. After hitting the playground “run” button, it now only takes about 3-4 seconds to get going instead of minutes. This kind of speed-up means it becomes far more practical to prototype SpriteKit and SceneKit elements in playgrounds before deploying them to real apps.
Update: Here’s another approach for setting up class instances:
infix operator •-> {}
// prepare class instance
func •-> <T>(object: T, f: (T) -> Void) -> T {
f(object)
return object
}
class MyClass {var (x, y, z) = ("x", "y", "z")}
let myInstance = MyClass() •-> {
$0.x = "NewX"
$0.y = "NewY"
}
What do you think?