Node.Physicsbody.Joints Downcasting Error

node.physicsBody.joints downcasting error

Update: This was a bug, and it's fixed in iOS 9 / OS X 10.11 — the code in the question just works now.

Leaving original answer text for posterity / folks using older SDKs / etc.


This looks like a bug — you should file it. Whether it should be considered a SpriteKit bug or a Swift bug is hard to say, but that's Apple's problem, not yours. :)

The problem is clear if you paste your code into a playground — your joint is actually a PKPhysicsJointWeld behind the scenes. That's some internal class that should be an implementation detail. In ObjC that's no problem, because casting in C is just a matter of telling the compiler, "trust me, this pointer is really an SKPhysicsJoint, so let me call physics joint methods (and nothing else) on it and and no one will be the wiser". Casting in Swift requires that there be a type hierarchy relationship between the casted types — and PKPhysicsJointWeld is not a subtype/subclass of SKPhysicsJoint, so the cast fails.

You can work around this issue by avoiding the cast to [SKPhysicsJoint]:

for joint in nodeA.physicsBody!.joints {
// do something else here
}

With this, you lose some type safety — joint is an AnyObject, so like ObjC's id type the compiler lets you call any method on it. (And it may fail at runtime if that object doesn't implement the method.) But at least it runs.

A further workaround: inside the loop, you can cast joint to SKPhysicsJoint. But since that cast is across the type hierarchy, you have to use unsafeBitCast:

for joint in nodeA.physicsBody!.joints {
let skJoint = unsafeBitCast(joint, SKPhysicsJoint.self)
// do stuff with skJoint
}

This gets you back a little bit of compile-time type "safety", in that thereafter the compiler will require anything you do with skJoint to be compatible with SKPhysicsJoint, but it's still inherently unsafe in that it depends on some hand-waving around runtime types that may not always hold. And you have to unsafeBitCast again to get to a particular joint subclass, without knowing which subclass it might be. (Again, this would be a good time to file a bug.)


(You might notice from pasting into a playground that physicsWorld is of an internal class, too: PKPhysicsWorld. So why doesn't that fail, too? When you use the physicsWorld property, all the type casting happens on the ObjC side, and Swift trusts whatever ObjC tells it. When you deal with the joints array, though, you have to do a type cast on the Swift side, and Swift is much more strict about type checking.)

node.physicsBody.joints downcasting error

Update: This was a bug, and it's fixed in iOS 9 / OS X 10.11 — the code in the question just works now.

Leaving original answer text for posterity / folks using older SDKs / etc.


This looks like a bug — you should file it. Whether it should be considered a SpriteKit bug or a Swift bug is hard to say, but that's Apple's problem, not yours. :)

The problem is clear if you paste your code into a playground — your joint is actually a PKPhysicsJointWeld behind the scenes. That's some internal class that should be an implementation detail. In ObjC that's no problem, because casting in C is just a matter of telling the compiler, "trust me, this pointer is really an SKPhysicsJoint, so let me call physics joint methods (and nothing else) on it and and no one will be the wiser". Casting in Swift requires that there be a type hierarchy relationship between the casted types — and PKPhysicsJointWeld is not a subtype/subclass of SKPhysicsJoint, so the cast fails.

You can work around this issue by avoiding the cast to [SKPhysicsJoint]:

for joint in nodeA.physicsBody!.joints {
// do something else here
}

With this, you lose some type safety — joint is an AnyObject, so like ObjC's id type the compiler lets you call any method on it. (And it may fail at runtime if that object doesn't implement the method.) But at least it runs.

A further workaround: inside the loop, you can cast joint to SKPhysicsJoint. But since that cast is across the type hierarchy, you have to use unsafeBitCast:

for joint in nodeA.physicsBody!.joints {
let skJoint = unsafeBitCast(joint, SKPhysicsJoint.self)
// do stuff with skJoint
}

This gets you back a little bit of compile-time type "safety", in that thereafter the compiler will require anything you do with skJoint to be compatible with SKPhysicsJoint, but it's still inherently unsafe in that it depends on some hand-waving around runtime types that may not always hold. And you have to unsafeBitCast again to get to a particular joint subclass, without knowing which subclass it might be. (Again, this would be a good time to file a bug.)


(You might notice from pasting into a playground that physicsWorld is of an internal class, too: PKPhysicsWorld. So why doesn't that fail, too? When you use the physicsWorld property, all the type casting happens on the ObjC side, and Swift trusts whatever ObjC tells it. When you deal with the joints array, though, you have to do a type cast on the Swift side, and Swift is much more strict about type checking.)

Swift: Unable to downcast AnyObject to SKPhysicsBody

After much trial and error, I have found a workaround to my problem. It turns out that you don't need to downcast at all to access the properties of the SKPhysicsBody, when the type is AnyObject.

for object in self.physicsBody.allContactedBodies()  {
if object.node??.name == "surface" {
isOnSurface = true
}
}

Swift convenience initializer extension for SKPhysicsBody

As @Cristik guessed in the comments, this is the same root problem as this question and this question: the public SKPhysicsBody class is a convenience interface for the private PKPhysicsBody class that provides its implementation.

In the past, this approach relied heavily on the "duck typing" behavior of Objective-C — as long as ClassA responds to all the same selectors as ClassB, you can call any of those selectors on a pointer whose static type (the type declared to the compiler in source code) is ClassB, even if the actual object at run time is really an instance of ClassA.

Swift is stricter about runtime type correctness than ObjC, so "duck typing" alone is not enough. Since iOS 9 / OS X 10.11, SpriteKit has some workarounds that allow PKPhysicsBody instances to better pretend to be SKPhysicsBody instances.

But those don't cover all cases — in particular, it doesn't catch (ObjC) [SKPhysicsBody alloc] returning a PKPhysicsBody instance, which means any attempt to add initializers to SKPhysicsBody in Swift will fail. (Because the ObjC alloc/init process is reduced to one initializer call in Swift.)

I'd consider this a bug and recommend filing it with Apple.


Edit/update: And until that bug gets fixed (it's been a year and some now), the workaround is to make your convenience "initializer" into a class method instead. (Or a global function if you must, but... ewww.)



Related Topics



Leave a reply



Submit