如何在 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")
}
}
初始化期间传输的 SKActions
是 runAction(_: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)
}
}
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")
}
}
初始化期间传输的 SKActions
是 runAction(_: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)
}
}