I use a subclass NSOperation to obtain serial execution of SKAction
as described in this question: How to subclass NSOperation in Swift to queue SKAction objects for serial execution?
I then modified the previous subclass in order to group animations for several nodes: https://stackoverflow.com/a/30600718/540780
At last, as I needed to run completion block after SKAction
completed, I slightly modified the code by using an array of struct instead an array of tuples.
struct ActionData {
let node:SKNode
let action:SKAction
let completion: () -> ()
init (node:SKNode, action:SKAction, completion:() -> () = {}) {
self.node = node
self.action = action
self.completion = completion
}
}
class ActionOperation : NSOperation
{
let _theActions:[ActionData]
var _finished = false // Our read-write mirror of the super's read-only finished property
var _executing = false // Our read-write mirror of the super's read-only executing property
var _numberOfOperationsFinished = 0 // The number of finished operations
/// Override read-only superclass property as read-write.
override var executing:Bool {
get { return _executing }
set {
willChangeValueForKey("isExecuting")
_executing = newValue
didChangeValueForKey("isExecuting")
}
}
/// Override read-only superclass property as read-write.
override var finished:Bool {
get { return _finished }
set {
willChangeValueForKey("isFinished")
_finished = newValue
didChangeValueForKey("isFinished")
}
}
// Initialisation with one action for one node
//
// For backwards compatibility
//
init(node:SKNode, action:SKAction) {
let donnees = ActionData(node: node, action: action, completion: {})
_theActions = [donnees]
super.init()
}
init (lesActions:[ActionData]) {
_theActions = lesActions
super.init()
}
func checkCompletion() {
_numberOfOperationsFinished++
logGeneral.debug(">> Block completed: \(self._numberOfOperationsFinished)/\(self._theActions.count) " + self.name!)
if _numberOfOperationsFinished == _theActions.count {
self.executing = false
self.finished = true
logGeneral.debug("Operation Completed: " + self.name!)
}
}
override func start()
{
if cancelled {
finished = true
return
}
executing = true
if name == nil {
name = "unknown"
}
_numberOfOperationsFinished = 0
var operation = NSBlockOperation()
var compteur = 0
for actionData in _theActions {
compteur++
var actionName = "???"
if let name = actionData.node.name {
actionName = name
}
logGeneral.debug("operation : \(compteur)/\(self._theActions.count) " + self.name! + " " + actionName)
operation.addExecutionBlock({
actionData.node.runAction(actionData.action,completion:{
actionData.completion()
self.checkCompletion() })
})
}
logGeneral.debug("Execute: " + self.name!)
NSOperationQueue.mainQueue().addOperation(operation)
}
}
The main idea is to elaborate animations by adding data of ActionData
type, append them to an array and transmit this array to the ActionAnimation
object.
In some random cases, some runAction
doesn complete: they start but some of them randomly do not complete. Here is a typical log where 6 block started but only 5 completed:
start(): operation : 1/6 ???
start(): operation : 2/6 suppressionPion1
start(): operation : 3/6 suppressionPion2
start(): operation : 4/6 suppressionPion3
start(): operation : 5/6 suppressionPion4
start(): operation : 6/6 suppressionGroupe1
start(): Execute: animerSupprimer
checkCompletion(): >> Block completed: 1/6
checkCompletion(): >> Block completed: 2/6
checkCompletion(): >> Block completed: 3/6
checkCompletion(): >> Block completed: 4/6
checkCompletion(): >> Block completed: 5/6
In this case only one runAction
failed to completed, in other case 2, 3 or none.
I really don't understand where the problem come from.
UPDATE
In some cases the app crashed with EXC_BAD_ACCESS
on main thread. This seems to be related to SKCSprite::update(double)
: