runAction on SKNode does not complete

2019-02-20 18:52发布

问题:

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):