如何在 Swift 中子类化 NSOperation 以将 SKAction 对象排队以进行串行执行?

How to subclass NSOperation in Swift to queue SKAction objects for serial execution?

Rob provided a great Objective-C solution for subclassing NSOperation 实现了SKAction对象的串行排队机制。我在自己的 Swift 项目中成功实现了这一点。

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's read-only finished property
    var _executing = false // Our read-write mirror of the super's 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's 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
            }
        }
    }
}

要使用 ActionOperation,请在您的客户端实例化一个 NSOperationQueue class 成员 class:

var operationQueue = NSOperationQueue()

在您的 init 方法中添加这一重要行:

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

然后当您准备好向其中添加 SKActions 时,它们 运行 串行:

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

是否需要随时终止操作:

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

希望对您有所帮助!

根据the document

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

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

所以我认为你必须:

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")
    }
}

初始化期间传输的 SKActionsrunAction(_:onChildWithName:).

时存在限制情况

在这种情况下,SKAction 的持续时间是瞬时的。

根据 Apple 文档:

This action has an instantaneous duration, although the action executed on the child may have a duration of its own.

我想对几个节点的动画进行分组。我首先通过将所有操作组合为一个来尝试上述解决方案,使用 runAction(_:onChildWithName:) 指定节点必须完成哪些操作。

不幸的是存在同步问题,因为在 runAction(_:onChildWithName:) 的情况下 SKAction 的持续时间是瞬时的。所以我必须找到另一种方法来在一次操作中对多个节点的动画进行分组。

然后我修改了上面的代码,添加了一个元组数组 (SKNode,SKActions)

此处显示的修改后的代码添加了为多个节点初始化操作的功能,每个节点都有自己的操作。

对于每个节点操作都是 运行 在它自己的块中使用 addExecutionBlock 添加到操作中。 当一个动作完成时,将执行一个完成块调用 checkCompletion() 以将它们全部加入。当所有操作都完成后,操作将标记为 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'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 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)

    }
}