node.physicsBody.joints downcasting error
Asked Answered
S

1

1

The following code gives an error - it appears the physics joints array have the class PKPhysicsJoint. Anyone have any ideas how I can iterate through the joints in Swift?

The documentation does say that physicsBody.joints should return an array of SKPhysicsJoint.

import SpriteKit

let scene = SKScene(size: CGSize(width: 200, height: 200))
let nodeA = SKNode()
let nodeB = SKNode()

nodeA.physicsBody = SKPhysicsBody(circleOfRadius: 20)
nodeB.physicsBody = SKPhysicsBody(circleOfRadius: 20)

scene.addChild(nodeA)
scene.addChild(nodeB)

let joint = SKPhysicsJointFixed.jointWithBodyA(nodeA.physicsBody, bodyB: nodeB.physicsBody, anchor: CGPointZero)
scene.physicsWorld.addJoint(joint)

for joint in nodeA.physicsBody!.joints as [SKPhysicsJoint] {
  // do something else here
}

gives error:

Execution was interrupted. reason: EXC_BAD_INSTRUCTION...
Sheltonshelty answered 13/2, 2015 at 14:30 Comment(1)
Try this: for joint in nodeA.physicsBody!.joints { .. }Decrepit
S
3

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.)

Sutherlan answered 13/2, 2015 at 18:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.