5

Is there some way in Swift that I can tell when an SKSpriteNode has actually been removed from the scene? I don't think it's actually done when removeFromParent is called, but instead I think it's done later, when Sprite-Kit thinks it's convenient to do so.

I'm trying to understand the full life cycle and I've noticed that a sprite can still be involved in contact and collisions in didBeginContact even after that sprite has been removed.

If I print out the contents of children (i.e. the array holding all the children of the scene) I see that the sprite is removed as soon as removeFromParent called, but the sprite is still available (at least, for this execution of the SK game loop).

Edit: This question came out from an earlier question of mine concerning didBeginContact being called multiple times for one contact (Sprite-Kit registering multiple collisions for single contact) and discovering that removing the sprite during the first contact did not prevent the subsequent contact(s). (Because SK has 'queued' to contacts in advance.) so I was wondering when the sprite is actually removed.

Community
  • 1
  • 1
Steve Ives
  • 7,894
  • 3
  • 24
  • 55
  • 2
    It is not the node which stays, but rather its physics body.. Node is removed immediately, but physics body is removed only when physics simulation is done. By the way I wrote an answer which shows how to use `NSNotifications` and extension to post a notification when a node is removed from a parent, and to observe that notification in different classes. But now I guess you don't need that lol – Whirlwind Nov 21 '16 at 10:46
  • 1
    @Whirlwind and I have written many answers on how to handle didBeginContact. I would not recommend using NSNotifications for such a thing unless you need a wide spread amount of classes to get this message. Instead, do not kill your node, just flag it that it is dying (I like using categoryBitMask flag 31, but you may use a separate variable), and have the parent just check the state of your node. Then on your didEndUpdate, do the actual kill so everything is killed together. – Knight0fDragon Nov 21 '16 at 12:29
  • 1
    I agree with @Knight0fDragon. Using NSNotifications should be used sparingly and they are meant for the cases when multiple objects are interested in a certain event. So you can notify them all at once, which is what I thought you are trying to achieve (issue a message when a node is removed so that other objects can be alarmed about that event). – Whirlwind Nov 21 '16 at 12:31
  • Knowing when the sprite is removed from wasn't the aim, but rather just a means to establish the node life cycle. So far I haven't been able to cause any issues in removing a node via removeFromParent in didBeginContact, even when there are subsequent contacts to be handled. – Steve Ives Nov 21 '16 at 12:59
  • You should definitely hit nil issues in your contact, perhaps you just have guards in place – Knight0fDragon Nov 21 '16 at 14:15
  • @Knight0fDragon Nope. I place three sprites on top of each other in didMoveToView and set up the physics bodies such that all contact each other. dBC is called 3 times - once for nodeA contacting nodeB, once for nodeB contacting nodeC and once for nodeC contacting nodeA. For each contact, I removeFromParent both nodes involved in the contact with no crashes. I think SK builds an array of SKSPhsyics contacts and calls dBC for each one. Removing the node does not affect subsequent calls to dBC, even involving the same node. As expected, dBC is not called in subsequent update() calls. – Steve Ives Nov 21 '16 at 14:21
  • @SteveIves, yes it will not remove the calls, all contacts will happen because the physics body is retained. However if you do body.node for the scenario of nodeC with nodeA and nodeA was removed in nodeA with nodeC. then body.node for A will be nil instead of nodeA – Knight0fDragon Nov 21 '16 at 14:24
  • @Knight - Even on the 3rd contact, I didn't get an error with "let nodeA = contact.bodyA.node; print("Removed /(bodyA!.name)") and the same for bodyB. the children array was definitely empty at this point too. I think that you are safe to do a simple removeFromParent with dBC, as SK will keep the node around long enough for this set of contact, but you might have to be careful if you delve too deeply into the properties of a node that might have been removed. I would love to see a code example where this causes a problem, as it's always better to have more understanding of the internals. – Steve Ives Nov 21 '16 at 14:38
  • 1
    Unless they changed something in iOS 10 this should not be the case, I will write an example later on today for you – Knight0fDragon Nov 21 '16 at 14:40
  • @Knight0fDragon Thanks - that'd be interesting. I tried to set the physicsBody for one of my nodes to nil in dBC and that broke things. ("Unexpectedly found nil..." on print("Removed /(bodyA!.name)"). – Steve Ives Nov 21 '16 at 14:44
  • @SteveIves It doesn't matter if you remove nodes like you described. Nothing will crash (except if you try to force unwrap something like this ` contact.bodyA.node!`). Or you are saying that after the node is removed, `contact.bodyA.node` is not `nil` ? – Whirlwind Nov 21 '16 at 15:11
  • 1
    @Whirlwind, I believe that is what he is saying. I assumed he typoed his print statement and meant to do `print("Removed /(nodeA!.name)") ` – Knight0fDragon Nov 21 '16 at 15:15
  • @Knight0fDragon Yeah, probably... There is no `name` property on `SKPhysicsBody` as I know. – Whirlwind Nov 21 '16 at 15:18
  • @Knight0fDragon sorry yes - print("Removed \(nodeA.name)") gave an error if I set the physicsbody to nil in dBC. – Steve Ives Nov 21 '16 at 15:22
  • 1
    well the issue is not setting physicsbody to nil, the issue is what happens to the node that the physicsbody is attached to – Knight0fDragon Nov 21 '16 at 15:24

3 Answers3

2

Am I missing the obvious? So even after removeFromParent the sprite is still around. However, it might well be because I have assigned the node to a temporary SKSpriteNode variable, then as long as that variable is around, there is a strong reference to the node, so it won't be deallocated. Also the SKPhysicsContact object itself will retain a reference to the physicsBody, which has a reference to the node which I think will also prevent allocation.

Update

To see when a sprite is actually released, use the deinit() method:

deinit {
    print("Invader of type \(type) deinitialised")
}

I think this can only be added in a subclass definition, not via an extension.

Having a variable with a strong reference to the node being removed will prevent the node from being de-allocated until that variable is itself removed or changed to refer to something else.

Steve Ives
  • 7,894
  • 3
  • 24
  • 55
  • Eheheh it happen ;-). The good thing is you've solved. So, good news. – Alessandro Ornano Nov 30 '16 at 15:07
  • 1
    @AlessandroOrnano Indeed. I've solved both of *my* problems with dBC being called multiple times for a single contact (although Knight threw me a couple of scenarios which I've not encountered myself but for which I'd have to handle dBC differently). Probably more importantly I've had to think about what's happening behind the scenes why why things work they way they do. – Steve Ives Nov 30 '16 at 15:13
  • 1
    I think you've nailed it with this temporary variable solution. This is how I was intuitively thinking about doing this, but without words to describe it. I needed a mind much smarter than mine to express it so I could more fully conceive and consider it, and I think I'm getting it, now. The reference in the SKSpriteNode variable, although not added to a scene, exists in the SpriteKit world, and is therefore addressable, can run actions, and call out to anything else in the world. Have I got this right? – Confused Dec 03 '16 at 02:35
1

If I've understand your question, I think it's not needed because you can always do:

if let myNode = self.childNode(withName: "//myNode") {
   // ok myNode exist
}

Hope it helps you, you can write this code wherever you think is necessary.

Update:

About the reformulation of your question take a look below to these comments.

Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
  • Alessandro - sorry - I've reworded my question a bit to add more context. I'm trying to get to the bottom of this "Never remove a sprite in didBeginContact" because it might cause a problem" advice, and I can't get it to cause a problem. – Steve Ives Nov 21 '16 at 10:33
  • Steve take a look to this answer http://stackoverflow.com/a/38497607/1894067 , in the same thread there is also your answer and let me know if I've understand what do you want to achieve ;) – Alessandro Ornano Nov 21 '16 at 10:37
  • I wasn't so much interested in where/when to remove the sprite, but where or when SK actually deallocates it. – Steve Ives Nov 30 '16 at 14:08
  • 1
    I think you should follow this order of events :- 1) stop/remove/disable (you decide) the physics from your node (physics should not be involved so there isnt a new next collision). 2) remove your node. You shouldn't have any problem after that. – Alessandro Ornano Nov 30 '16 at 14:18
1

I have a suggestion after reading through the comments... move the node to a place outside of the playable area of your game, then remove it from parent. At this point you don't have to worry about when the physics body gets removed or when SK handles it. Or you could set the physicsBody? to nil at the same time, or use a bitmask flag as KoD suggested.

You can override all of the functions in the SK loop and check to see exactly when your pb is removed: https://developer.apple.com/library/content/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Actions/Actions.html

Fluidity
  • 3,985
  • 1
  • 13
  • 34
  • I've checked those docs but couldn't see anything about node deallocation. – Steve Ives Nov 30 '16 at 10:31
  • @SteveIves I meant after you override the methods, you can run a check such as Alessandro suggested to find out when / where your stuff dies automatically. – Fluidity Nov 30 '16 at 10:33