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
Swift: Nil Error When Using Self.Moc.Save() to Save in Core Data
How Is a Global Variable Set to Private Understood in Swift
Why Can't I Use 'Type' as the Name of an Enum Embedded in a Struct
How to Decode Utf8-Literals Like "\Xc3\Xa6" in Swift 5
How to Make Sure Data in Variable Is Loaded Before Using It
How to Make a Uiview Grow in Uiview.Animate() with an Accurate Corner Radius
How to Change Values Inside Array Without a Loop Swift
Unexpectedly Found Nil While Unwrapping an Optional Value Parsing JSON Swift
Using the Swift If Let with Logical and Operator &&
Apple Vision Framework - Text Extraction from Image
Printed Double Value Differs from Originally Set Value
Swift Format Text Field When User Is Typing
Compare Value from Two Struct in Swift
Swift How to Design Uiwebview Auto Resize Full Screen in Story Board
Make Swiftui Rectangle Same Height or Width as Another Rectangle
Enum Method Returning a Dynamic Type
.Sink Is Not Returning the Promise Values from a Future Publisher
Swift - Creating Shadow with 2 Different Colours for Imageview