Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SpriteKit. A change from optional to force unwrap crashes the app

I’m following a tutorial for SpriteKit that has a problem with an IF statement. The logic of the line is as follows: If the bullet and the asteroid collide then remove them.

if body1.categoryBitMask == PhysicsCategories.bullet && body2.categoryBitMask == PhysicsCategories.asteroid {
   // remove bullet and asteroid
}

The problem arises when trying to make sure that the asteroid (body2.node) is inside the playable area before it can get shut down. For that, the author adds the following:

body2.node?.position.y < self.size.height

Making the complete IF statement as follows:

if body1.categoryBitMask == PhysicsCategories.bullet && body2.categoryBitMask == PhysicsCategories.asteroid && body2.node?.position.y < self.size.height {
   // remove bullet and asteroid
}

Apparently that line works with Swift 2 however Swift 3 makes a correction changing the position from an optional and force unwraps the position.

    if body1.categoryBitMask == PhysicsCategories.bullet && body2.categoryBitMask == PhysicsCategories.asteroid && body2.node!.position.y < self.size.height {
        // remove bullet and asteroid        
    }

By force unwrapping the position, the app crashes “I THINK” when the three bodies collide. It is really difficult to tell when looking at the screen.

I’m testing the code below and I have not encounter any problems as of yet. Do you guys think that the fix below will work? What I'm thinking is, if I make sure the body2.node is not nil, then there is no reason why the app should crash since is not going to encounter a nil upon trying to force unwrap it.

if body1.categoryBitMask == PhysicsCategories.bullet && body2.categoryBitMask == PhysicsCategories.asteroid {
    // If the bullet has hit the asteroid
  if body2.node != nil {
    if ( body2.node!.position.y < self.size.height ) {
       // remove bullet and asteroid
    }           
  }           
}

Or else, if there another way you guys can suggest a different way to write the original IF Statement?

Thanks

like image 394
GIJoeCodes Avatar asked Jan 05 '23 20:01

GIJoeCodes


2 Answers

Yes, the if != nil statement (as it is currently written) will protect against force-unwrap-induced crashes.

An alternative is to use the if let syntax in Swift:

if body1.categoryBitMask == PhysicsCategories.bullet && body2.categoryBitMask == PhysicsCategories.asteroid {
    // If the bullet has hit the asteroid
    if let body2Node = body2.node {
        if body2Node.position.y < self.size.height {
           // remove bullet and asteroid
        }           
    }           
}

The benefit is that it removes the ! from your code, and more clearly connects the nil check with the variable you are using later.

like image 53
nathangitter Avatar answered Jun 01 '23 17:06

nathangitter


nathan has the right answer, but a better alternative would be to use a guard instead to protect your function:

...

guard let  body1Node = body1.node, let  body2Node = body2.node else {return}
//Beyond this point we need to guarentee both nodes exist

if body1.categoryBitMask == PhysicsCategories.bullet && body2.categoryBitMask == PhysicsCategories.asteroid {
    // If the bullet has hit the asteroid
    if body2Node.position.y < self.size.height {
       // remove bullet and asteroid
    }           

}

By using a guard, we reduce nesting, and we know that beyond this point, the physics body must contain a node, unless we remove it before the function ends, which will eliminate any further need to check if the node exists.

As a side note, I always recommend to remove nodes at the end of your update phase to avoid issues like this, and instead just mark the node somehow that you will no longer be using it.

like image 30
Knight0fDragon Avatar answered Jun 01 '23 15:06

Knight0fDragon