5

I have a helper method which extends SKPhysicsContact

extension SKPhysicsContact {

    /// - returns: `[SKPhysicsBody]` containing all the bodies that match `mask`
    func bodiesMatchingCategory(mask: UInt32) -> [SKPhysicsBody] {
        let bodies = [bodyA, bodyB]
        return bodies.filter { ($0.categoryBitMask & mask) != 0 }
    }
}

in didBeginContact() I call this method on the passed in contact.

func didBeginContact(contact: SKPhysicsContact) {
    let ballMask: UInt32 = 0x1 << 2
    let ball = contact.bodiesMatchingCategory(ballMask)
...

I get this error message sometimes (like 1 in 5) which crashes the app:

-[PKPhysicsContact bodiesMatchingCategory:]: unrecognized selector sent to instance 0x165f2350

I looked up PKPhysicsContact and it's part of a private framework (link). SKPhysicsContact looks like it's just an empty class definition which exposes only certain properties of PKPhysicsContact.

I feel like this is an Objective-C hack on the SpriteKit team that breaks Swift's strong typing.

Help?

How to make sure I always get SKPhysicsContact back?


I added a check to test for SKPhysicsContact

let test = contact as Any
print("Test is: \(test)")
guard test is SKPhysicsContact else {
    return
}

Which correctly catches the type mis-match.

In fact, it NEVER returns a SKPhysicsContact!!?


I've tried doing this in Objective-C (as suggested by responder) and I'm getting the same result.

I have a discussion on the Apple Dev Forums which may provide future answer-seekers some help.

Here's the Objective-C code for reference:

@interface SKPhysicsContact (MatchingBodies)

- (NSArray *)bodiesMatchingCategory:(UInt32)category;

@end

@implementation SKPhysicsContact (MatchingBodies)

- (NSArray *)bodiesMatchingCategory:(UInt32)category {
    NSArray *bodies = @[self.bodyA, self.bodyB];

    NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(SKPhysicsBody *body, NSDictionary *bindings) {
        return (body.categoryBitMask & category) != 0;
    }];
    NSArray *matching = [bodies filteredArrayUsingPredicate:predicate];
    return matching;
}

@end

Called Here:

-(void)didBeginContact:(SKPhysicsContact *)contact
{
    static const uint32_t MarbleContact = 0x1 <<1;  // 2
    static const uint32_t GoalContact = 0x1 <<2;    // 4

    SKPhysicsBody *ball = [contact bodiesMatchingCategory:MarbleContact].firstObject;
    NSLog(@"Ball: %@", ball);
    ...

Returns this crash:

-[PKPhysicsContact bodiesMatchingCategory:]: unrecognized selector sent to instance 0x17dad9e0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[PKPhysicsContact bodiesMatchingCategory:]: unrecognized selector sent to instance 0x17dad9e0'

Added Bug Report for Apple, #23332190

Stephen Furlani
  • 6,794
  • 4
  • 31
  • 60
  • Where is `bodiesMatchingCategory` defined? Is that in an extension of `SKPhysicsContact`? – Ben Kane Oct 29 '15 at 19:43
  • @BenKane yes, it's at the top of my question – Stephen Furlani Oct 29 '15 at 20:00
  • Oh, wow. Don't know how I missed that one :) Anyway, see my answer. I went ahead with the assumption that that was the case. – Ben Kane Oct 29 '15 at 20:00
  • Please report back if you file a bug report or not. I will if you don't. I probably will either way, but it'd still be nice to know. – Ben Kane Oct 29 '15 at 21:36
  • @BenKane I've reported it on the dev forums, but you should file a bug report anyway (with the evidence of the category) https://forums.developer.apple.com/message/81989 – Stephen Furlani Oct 30 '15 at 13:46

1 Answers1

5

I get the same error with this simple code:

extension SKPhysicsContact {
    func bodiesMatchingCategory(mask: UInt32) -> [SKPhysicsBody] {
        let bodies = [bodyA, bodyB]
        return bodies.filter { ($0.categoryBitMask & mask) != 0 }
    }
}

let contact = SKPhysicsContact()
let body = contact.bodiesMatchingCategory(0)

The problem is, the type of contact is PKPhysicsContact (as you've noticed), even when you explicitly tell it to be an SKPhysicsContact, and the extension is on SKPhysicsContact. You'd have to be able to make an extension to PKPhysicsContact for this to work. From this logic, we can say that no instance methods will work in SKPhysicsContact extensions at the moment. I'd say it's a bug with SpriteKit, and you should file a radar. Class methods still work since you call them on the class itself.

In the meantime, you should be able to move that method into your scene or another object and call it there successfully.


For the record, this is not a Swift-specific problem. If you make the same method in an Objective-C category on SKPhysicsContact you'll get the same crash.

Ben Kane
  • 9,331
  • 6
  • 36
  • 58
  • 1
    For the record, if you put `let contact = SKPhysicsContact()` into a playground, you'll see the returned type is `PKPhysicsContact`. So I'd guess that's the real issue. It's never actually an `SKPhysicsContact` (as you've observed), which explains why the instance method doesn't work but the class method does. Definitely seems like a bug. – Ben Kane Oct 29 '15 at 20:05
  • 1
    @stephen Now that I saw your implementation, I took out the class func suggestion, since you wouldn't have access to bodyA and bodyB. You'll have to call this method somewhere else (scene maybe. a physics manager class maybe). From what I can see, the type of SKPhysicsContact() is always `PKPhysicsContact`, even if you explicitly try to cast, or tell it to be `SKPhysicsContact` on assignment. That explains why your extension method isn't working. It would have to be an extension on `PKPhysicsContact`, which you won't be able to do AFAIK. – Ben Kane Oct 29 '15 at 20:24
  • @stephen I edited my original answer to actually make some sense and provide a little explanation. Had some bad info in the original one, sorry about that. – Ben Kane Oct 29 '15 at 20:34
  • 1
    @stephen Just for clarity, this isn't an issue with Swift, it's one with SpriteKit. The same crash occurs if you do this with a category in Objective-C. – Ben Kane Oct 29 '15 at 21:35