10
votes

A SKSpriteNode is child of a SKNode and is placed on an array of SKSpriteNode for storage purposes.

This SKSpriteNode is deleted using an animation. At the end of this animation a completion block is executed to perform some statements...

The deletion must occurred both in the SKSpriteNode parent’s and in the array. Depending on the order of these 2 deletions the result is correct or not:

  • if SKSpriteNodeis deleted from 1/ the array then 2/ from the SKNode parent’s, the completion block is executed.
  • if the inverse order, 1/ SKNode parent’s then 2/ the array, the completion block is not executed.

Why this behavior?

for position in listOfPositions {

   theSprite:SKSpriteNode = theGrid[position]

   /// the SKSpriteNode referenced by theSprite :
   /// - belongs to an array of SKSpriteNode: theGrid
   /// - belongs to a SKNode: theGameLayer
   ///
   /// In other words this SKSpriteNode is referenced twice
   ///

   let theActions = SKAction.sequence([
      /// Some actions here
      /// ...

      /// Remove theSprite from the Grid
      /// - position is an instance of a structure of my own
      /// - theGrid is accessed via a subscript
      ///
      SKAction.runBlock({self.theGrid[position] = nil}),

      /// remove theSprite from it's parent
      SKAction.removeFromParent(),
   ])

   theSprite.runAction(theActions,completion:{NSLog("Deleted")})
}

The completion message is displayed.

Now if removeFromParent is placed before the remove from theGrid action, as follow, the completion does not execute:

let theActions = SKAction.sequence([
   /// Some actions here
   /// ...

   /// remove theSprite from it's parent
   SKAction.removeFromParent(),

   /// remove theSprite from the Grid
   SKAction.runBlock({self.theGrid[position] = nil}),
])
1
try this: SKAction.runBlock({self.theGrid[self.position] = nil}),Özgür Ersil
Adding self to positionis not relevant in the situation: it leads to a compilation error.Dominique Vial
i recommend to write self because i think it is like a scope problem.Özgür Ersil
Your recommendation is OK but does not apply to this situation as the scope for position is correct. It seems to be more related on how removeFromParent manage the execution of the completion block.Dominique Vial
I have updated the question by adding explanations to better understand the context.Dominique Vial

1 Answers

11
votes

From the SKAction Reference:

An SKAction object is an action that is executed by a node in the scene (SKScene)...When the scene processes its nodes, actions associated with those nodes are evaluated.

In other words, the actions for a node is run if and only if that node is in the scene. By calling removeFromParent, you remove the node from the scene, the runBlock action is never called (since the node is no longer in the scene), and thus the sequence is never finished. Since the sequence doesn't finish, the completion block doesn't get called.

I would suggest moving the removeFromParent call to the completion block for safety's sake. Something like this feels safer:

for position in listOfPositions {

   let theSprite: SKSpriteNode = theGrid[position]

   /// the SKSpriteNode referenced by theSprite :
   /// - belongs to an array of SKSpriteNode: theGrid
   /// - belongs to a SKNode: theGameLayer
   ///
   /// In other words this SKSpriteNode is referenced twice
   ///

   let theActions = SKAction.sequence([
      /// Some actions here
      /// ...

      /// Remove theSprite from the Grid
      /// - position is an instance of a structure of my own
      /// - theGrid is accessed via a subscript
      ///
      SKAction.runBlock({self.theGrid[position] = nil})
   ])

   theSprite.runAction(theActions) {
      /// remove theSprite from it's parent
      /// Might need to weakly reference self here
      theSprite.removeFromParent(),
      NSLog("Deleted")
   }
}

TL;DR
The sequence doesn't complete, so the completion block for sequence doesn't get called.