How to subclass NSOperation in Swift for a SKAction object queue for sequential execution?

Rob provided an excellent Objective-C solution for the NSOperation subclass to create a consistent queuing mechanism for SKAction objects. I implemented this successfully in my own Swift project.

import SpriteKit class ActionOperation : NSOperation { let _node: SKNode // The sprite node on which an action is to be performed let _action: SKAction // The action to perform on the sprite node var _finished = false // Our read-write mirror of the super read-only finished property var _executing = false // Our read-write mirror of the super read-only executing property /// 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") } } /// Save off node and associated action for when it time to run the action via start(). init(node: SKNode, action: SKAction) { // This is equiv to ObjC: // - (instancetype)initWithNode(SKNode *)node (SKAction *)action // See "Exposing Swift Interfaces in Objective-C" at https://developer.apple.com/library/mac/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-XID_35 _node = node _action = action super.init() } /// Add the node action to the main operation queue. override func start() { if cancelled { finished = true return } executing = true NSOperationQueue.mainQueue().addOperationWithBlock { self._node.runAction(self._action) { self.executing = false self.finished = true } } } } 

To use ActionOperation, instantiate an element of the NSOperationQueue class in your client class:

 var operationQueue = NSOperationQueue() 

Add this important line to your init method:

 operationQueue.maxConcurrentOperationCount = 1; // disallow follow actions from overlapping one another 

And then, when you are ready to add SKActions to it so that they run in serial:

 operationQueue.addOperation(ActionOperation(node: mySKNode, action: mySKAction)) 

If you need to terminate at any point:

 operationQueue.cancelAllOperations() // this renders the queue unusable; you will need to recreate it if needing to queue anymore actions 

Hope this helps!

+5
source share
3 answers

According to the document :

In your custom implementation, you must generate KVO notifications for the isExecuting key key whenever the execution status of your operation object changes.

In your custom implementation, you must generate KVO notifications for the isFinished key path whenever the ready state of your operation object changes.

So, I think you need:

 override var executing:Bool { get { return _executing } set { willChangeValueForKey("isExecuting") _executing = newValue didChangeValueForKey("isExecuting") } } override var finished:Bool { get { return _finished } set { willChangeValueForKey("isFinished") _finished = newValue didChangeValueForKey("isFinished") } } 
+11
source

I want to group animations for multiple nodes. First I tried the solution above, combining all the actions in one, using runAction(_:onChildWithName:) to indicate which actions should be performed using node.

Unfortunately, there were problems with synchronization, because in the case of runAction(_:onChildWithName:) duration for SKAction instantaneous. So I have to find another way to group animations for several nodes in one operation.

Then I changed the code above by adding an array of tuples (SKNode,SKActions) .

The modified code presented here adds a function to start an operation for several nodes, each of which has its own actions.

For each action, a node executes its own block inside it, added to the operation using addExecutionBlock . When the action is completed, a completion block is executed that calls checkCompletion() to join them all. When all actions are completed, the operation will be marked as finished .

 class ActionOperation : NSOperation { let _theActions:[(SKNode,SKAction)] // The list of tuples : // - SKNode The sprite node on which an action is to be performed // - SKAction The action to perform on the sprite node var _finished = false // Our read-write mirror of the super read-only finished property var _executing = false // Our read-write mirror of the super read-only executing property var _numberOfOperationsFinished = 0 // The number of finished operations override var executing:Bool { get { return _executing } set { willChangeValueForKey("isExecuting") _executing = newValue didChangeValueForKey("isExecuting") } } 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) { _theActions = [(node,action)] super.init() } init (theActions:[(SKNode,SKAction)]) { _theActions = theActions super.init() } func checkCompletion() { _numberOfOperationsFinished++ if _numberOfOperationsFinished == _theActions.count { self.executing = false self.finished = true } } override func start() { if cancelled { finished = true return } executing = true _numberOfOperationsFinished = 0 var operation = NSBlockOperation() for (node,action) in _theActions { operation.addExecutionBlock({ node.runAction(action,completion:{ self.checkCompletion() }) }) } NSOperationQueue.mainQueue().addOperation(operation) } } 
+1
source

There is a case of limitation when SKActions passed during initialization is runAction(_:onChildWithName:) .

In this case, the duration of this SKAction instantaneous.

According to Apple documentation:

This action has an instantaneous duration, although an action performed on a child can have its own duration.

0
source

Source: https://habr.com/ru/post/1212836/


All Articles