Pete Barber from C#, C++, Windows & other ramblingsMy Swift and SpriteKit exploration continues. At the moment I'm writing the collision handling code.
Rather than derive game objects from SKSpriteNode with each derived class containing the code for handling collisions with the other types of game objects I'm following something akin to a Component-Entity model.
I have per-game-object handler classes of which an instance of each is stored in the actual SKSpriteNode's userData dictionary. In turn each handler instance has a reference to the SKSpriteNode that references it. Given ARC is used this is a classical strong-reference chain which will prevent memory from being freed. The usual solution to this in Objective-C is to have one of the references be weak. In Swift there are two types of weak references: weak which is the same as in Objective-C and unowned (which I think is new). The difference is that an unowned reference can never be nil, i.e. it's optional whether a weak pointer reference an object but an unowned pointer must always reference something. As such the member variable is always defined using let and must be initialized, i.e. an init method is required.
The following code shows how I was intending to implement this. There is the strong reference from node.UserData to PhysicsActions and then the unowned reference back again from PhysicsActions.
unowned let node : SKSpriteNode
init(associatedNode : SKSpriteNode)
// Store really weak (unowned) reference
self.node = associatedNode
func onContact(other : PhysicsActions)
// Do stuff with node
class func makeNode(imageNamed name: String) -> SKSpriteNode
let node = SKSpriteNode(imageNamed: name)
node.userData = NSMutableDictionary()
// Store strong reference
node.userData["action"] = makeActionFn(node)
However, when I went to use this code it crashed within the onContact method when it attempted to use the node. Changing this the reference type from unowned to weak fixed this, e.g.
weak let node : SKSpriteNode?
This verified that the rest of the code was ok so this seemed to look like another Swift/Objective-C interoperability issue. Firstly, I made a pure Swift example which is a simplified version from the The Swift Programming Language book.
var bar : Bar?
func addBar(bar: Bar)
self.bar = bar
unowned let foo : Foo
init(foo : Foo)
self.foo = foo
var foo : Foo? = Foo()
var bar = Bar(foo: foo!)
Which works and results in:
foo:C14XXXUnownedTest3Foo (has 1 child)
Ok, not a fundamental problem but let's try having an unowned reference to an Objective-C class which is just like the real case as that's what SKSpriteNode is.
@interface Foo2 : NSObject
return [super init];
unowned let foo2 : Foo2
init(foo2 : Foo2)
self.foo2 = foo2
var foo2 = Foo2()
var bar2 = Bar2(foo2: foo2)
Which when foo2.f() is invoked results in:
0x100142420: pushq %rbp
0x100142421: movq %rsp, %rbp
0x100142424: leaq 0x17597(%rip), %rax ; "attempted to retain deallocated object"
0x10014242b: movq %rax, 0x348be(%rip) ; gCRAnnotations + 8
0x100142433: nopw %cs:(%rax,%rax)
Again, changing unowned let foo2 : Foo2 to weak var foo2 : Foo2? works.
I can't explain what the actual problem is but it looks like the enhanced weak reference support (unowned) only works with pure Swift classes. If you have cyclic references to Objective-C classes from Swift then don't use unowned. In fact writing the previous sentence led me to try the following:
let orig = Foo2()
unowned let other = orig
No cyclic references, just an ordinary reference counted instance of Foo2 (the Objective-C class) which is then assigned to an unowned reference. The final call to println will keep the instance around until the end. This crashes as per the previous example when the other variable is accessed. Changing the type assigned to orig from Foo2 (Objective-C) to Foo (Swift) make it work.
Therefore it seems unowned should not be used to refer to Objective-C classes, just Swift classes.