My 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 references 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
.
class PhysicsActions
{
unowned let node : SKSpriteNode
init(associatedNode : SKSpriteNode)
{
self.node = associatedNode
}
func onContact(other : PhysicsActions)
{
}
}
class func makeNode(imageNamed name: String) -> SKSpriteNode
{
let node = SKSpriteNode(imageNamed: name)
node.userData = NSMutableDictionary()
node.userData["action"] = makeActionFn(node)
return 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.
class Foo
{
var bar : Bar?
func addBar(bar: Bar)
{
self.bar = bar
}
}
class Bar
{
unowned let foo : Foo
init(foo : Foo)
{
self.foo = foo
}
func f()
{
println("foo:\(foo)")
}
}
var foo : Foo? = Foo()
var bar = Bar(foo: foo!)
foo!.ç(bar)
bar.f()
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.
Foo2.h
@interface Foo2 : NSObject
@end
Foo2.m
@implementation Foo2
-(id)init
{
return [super init];
}
@end
main.swift
class Bar2
{
unowned let foo2 : Foo2
init(foo2 : Foo2)
{
self.foo2 = foo2
}
func f()
{
println("foo2:\(foo2)")
}
}
var foo2 = Foo2()
var bar2 = Bar2(foo2: foo2)
bar2.f()
Which when foo2.f()
is invoked results in:
libswift_stdlib_core.dylib`_swift_abortRetainUnowned:
0x100142420: pushq %rbp
0x100142421: movq %rsp, %rbp
0x100142424: leaq 0x17597(%rip), %rax ; "attempted to retain deallocated object"
0x10014242b: movq %rax, 0x348be(%rip) ; gCRAnnotations + 8
0x100142432: int3
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
println("other:\(other)")
println("orig:\(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.