4

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

GIJoeCodes
  • 1,680
  • 1
  • 25
  • 28
  • Very likely that game can crash in the case when three bodies make a contact at the same time and one or more of them being removed in didBegin(_ contact) without waiting for a physics engine to process all contacts that happened. I just made an explanation about that here : http://stackoverflow.com/a/41452239/3402095 Checking if body.node is nil before force-unwrapping will prevent a crash. Also that way, some didBegin(_ contact) calls might be skipped, so you have to check if that is a valid option for your game. – Whirlwind Jan 03 '17 at 23:11
  • 1
    To get the low down on `guard`, have a look at this: http://stackoverflow.com/a/39263210/2109038 – Confused Jan 04 '17 at 03:31
  • For quite a good rundown on what `if let` is doing, have a look here: http://stackoverflow.com/a/36303241/2109038 – Confused Jan 04 '17 at 03:36

2 Answers2

5

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.

nathangitter
  • 9,607
  • 3
  • 33
  • 42
4

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.

Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
  • where does the return goto, and how does that ensure there's a safe handling of there not being the requirement met? – Confused Jan 04 '17 at 02:47
  • @Confused stop leaving useless comments, if you do not know what return does, google it or go read a book – Knight0fDragon Jan 04 '17 at 03:04
  • 1
    It's a serious question. Nathan's answer handles the failure. Yours, so far as I can tell, despite your claims of `guard's` superiority, doesn't seem to handle it. – Confused Jan 04 '17 at 03:08
  • Go look up what the definition of guard is in the english dictionary – Knight0fDragon Jan 04 '17 at 03:10
  • I'm much more interested in the definition of `return` in the context of its use in your example, since that's the bit that's unclear. Where does it return to? – Confused Jan 04 '17 at 03:11
  • Go back to my first comment to you – Knight0fDragon Jan 04 '17 at 03:12
  • You still don't explain or describe where `return` goes. In light of your suggestion that this is the superior technique, and that the OP obviously knows nothing of `guard`, or how to use `if let` for forced unwrapping, how do you imagine he's going to know where `return` goes any better than I don't? I'm gently pointing out that making the presumption to preach requires more thorough descriptions of consequences and actions, and a little more explanation. Otherwise it's just noise without meaning. – Confused Jan 04 '17 at 03:16
  • Because if you do not know what return does, you do not belong on SO yet, and you need to hit tutorials – Knight0fDragon Jan 04 '17 at 03:18
  • Again, within the context of a `guard` statement, in your example, and since you do presume to belong here, and know where it goes, in YOUR example, do you think you could be so kind as to illustrate, illuminate or articulate? – Confused Jan 04 '17 at 03:19
  • And take a very careful look at the question. The OP is going through a tutorial, and suffering three main problems. 1. Forced unwrapping and optionals and their consequences are new to him. 2. Swift 2 to Swift 3 is a little different. 3. You're suggesting a better alternative but not fully explaining how it works, and what it does in terms of dealing with a failure. `if let` has the advantage of transparently falling through. What does `return` do? – Confused Jan 04 '17 at 03:22
  • I keep telling you this, SO is not a tutorial site, it is a QA site. We are allowed to assume that a person knows basic key words to the language they are working with, or has the capacity to look up a basic keyword. Otherwise I would have to describe what `if` `let` `else` `==` and every other little keyword means. Stop filling peoples comments with needless banter, you only hurt the community, you offer zero help by doing this. – Knight0fDragon Jan 04 '17 at 03:24
  • 1
    And SO has linking. And a history of answers that are somewhat contributory and assistive to understanding a suggestion of a newer, superior technique. eg: http://stackoverflow.com/a/39263210/2109038. Or you could spend an extra 20 seconds of typing to provide information on how `guard` creates a variable that exists outside its `else` scope and reveal the power of the `guard's` context (scope) being within the function, and therefore the forcefulness of the use of `return` in the `else{}` closure, or you could spend more time attempting to tutor me on what you think SO is, and is not. – Confused Jan 04 '17 at 03:30
  • That link is a terrible way to describe if let vs guard because that is not how it works, guard is used when you want to keep a variable in the current scope, if let is when you want to place a variable in a new scope. This question is not what is the difference between guard and if let. If you feel the link would improve the answer, then you add it as a comment. That is what the comments are for. My answer is piggy backing off of nathans, which is why I gave him the credit. I would have done it as a comment, but showing code is terrible in a comment. – Knight0fDragon Jan 04 '17 at 03:37
  • Look up, to the original question. I have provided the link in my comment to you, and one other that details some of the differences between `if let` and `guard`. If you know how to articulate this stuff better, why don't you do it? – Confused Jan 04 '17 at 03:39
  • Because unlike you, a lot of programmers do not want to have their hand held, they do not want to have to read paragraphs of useless text. They want to read the code and understand the code the way the computer reads and understands the code. It is possible to over saturate an answer you know. – Knight0fDragon Jan 04 '17 at 03:47
  • So it's your expert opinion that Jose is sufficiently _au fait_ in Swift optionals, unwrapping and casting that he's gonna just grok `guard's` use of `return` from your existing answer's content and your supposition that it's better than `if let`, something he clearly knew nothing about at the time of asking his question? – Confused Jan 04 '17 at 04:56
  • It is my expert opinion that Jose is sufficient to know what `return` does since Jose can type in English – Knight0fDragon Jan 04 '17 at 04:59
  • I'm actually confused too. If `return` exits the function, then how does the function handle the case of not having the detection? @Confused , @Knight0fDragon – Fluidity Jan 04 '17 at 06:44
  • @Fluidity what? You are returning the function because you are missing necessary data – Knight0fDragon Jan 04 '17 at 06:52
  • @Knight0fDragon oh, ok. You didn't mention that in the answer though :( – Fluidity Jan 04 '17 at 06:55
  • sort of, guard is explained as being for early exit...not necessarily WHY you want to early exit – Fluidity Jan 04 '17 at 07:01
  • ... because beyond the point of the guard we need to know that the physicsbody contains a node, this is in the answer – Knight0fDragon Jan 04 '17 at 07:02
  • oh ok, that makes more sense now... but nathans answer allows for expansion like if he wanted to add another case... with return you would have to have to restructure everything as program evolved..rite? – Fluidity Jan 04 '17 at 07:08
  • @Fluidity no nathans answer means you become prone to error because you need to consistently check if it exists, you use the guard to divide your method up, anything not involving a node goes before the guard, anything involving the node goes after. It allows for cleaner, safer, maintainable code. This is why guard was introduced to swift – Knight0fDragon Jan 04 '17 at 07:12
  • ok i can see it seems cool, but what if you needed to set up a handling of not finding body1 one way, then body2 another way. return would just end the code right? am I supposed to put the handling code before guard there? – Fluidity Jan 04 '17 at 07:19
  • Are you talking about rearranging body 1 and body 2 so that you can guarentee what Bodies A and B are? If so, this involves bodies not the nodes so would go before the guard – Knight0fDragon Jan 04 '17 at 07:21
  • so like you said that all code involving nodes goes before guard, but if i wanted to print a message that body1 wasn't found then I can't put it after the guard, because the guard would return the function – Fluidity Jan 04 '17 at 07:22
  • You could put it right in the else statement before you do the return if you want – Knight0fDragon Jan 04 '17 at 07:22
  • i guessit was just more obvious to do an `else` from nathans but now that we can do stuffin the guard that's cool too – Fluidity Jan 04 '17 at 07:23
  • oh but wait, what if we wanted to print that body1node was found, but bodynode2 wasn't found... is that needing more guards? then we would need two returns. Wouldn't that be repeating code? – Fluidity Jan 04 '17 at 07:25
  • Yes, you want to reduce as much nesting as possible, nesting (curly brace after curly brace) makes code hard to read, and adds overhead to the call stack, meaning inefficient code. In any other language you would do an if not type statement, this is pretty much what a guard is – Knight0fDragon Jan 04 '17 at 07:26
  • but curly braces are pretty :) {{{{ }}}}{}}{{}} – Fluidity Jan 04 '17 at 07:27
  • 1
    You can do 2 different guards, or check inside the else which variable is nil – Knight0fDragon Jan 04 '17 at 07:28
  • Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackoverflow.com/rooms/132259/discussion-on-answer-by-knight0fdragon-spritekit-a-change-from-optional-to-forc). – Bhargav Rao Jan 04 '17 at 07:58