How to subclass NSOperation in Swift to queue SKAction objects for serial execution?
According to 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.
So I think you have to:
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")
}
}
add SKAction to Sprite queue run one after another
Generally, you can make SKActions
run concurrently using the group
method, and have them run sequentially using the sequence
method.
But if you need a queuing system, rather than building your own, use the native operation queue to do this for you. So you can create a serial operation queue and add operations to it. The issue is that you don't want an operation to complete until the SKAction
does.
So, you can wrap your SKAction
in a concurrent NSOperation
subclass that only completes when the SKAction
does. Then you can add your operations to a serial NSOperationQueue
, and then it will won't start the next one until it finishes the prior one.
So, first, create an ActionOperation
(subclassed from NSOperation
) that looks like:
// ActionOperation.h
#import <Foundation/Foundation.h>
@class SKNode;
@class SKAction;
@interface ActionOperation : NSOperation
- (instancetype)initWithNode:(SKNode *)node action:(SKAction *)action;
@end
and
// ActionOperation.m
#import "ActionOperation.h"
@import SpriteKit;
@interface ActionOperation ()
@property (nonatomic, readwrite, getter = isFinished) BOOL finished;
@property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
@property (nonatomic, strong) SKNode *node;
@property (nonatomic, strong) SKAction *action;
@end
@implementation ActionOperation
@synthesize finished = _finished;
@synthesize executing = _executing;
- (instancetype)initWithNode:(SKNode *)node action:(SKAction *)action
{
self = [super init];
if (self) {
_node = node;
_action = action;
}
return self;
}
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.node runAction:self.action completion:^{
self.executing = NO;
self.finished = YES;
}];
}];
}
#pragma mark - NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
@end
You could then, for example, create a serial queue during the initialization process:
self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 1;
You can then add the operations to it:
SKAction *move1 = [SKAction moveTo:point1 duration:2.0];
[self.queue addOperation:[[ActionOperation alloc] initWithNode:nodeToMove action:move1]];
and you can later add more actions:
SKAction *move2 = [SKAction moveTo:point2 duration:2.0];
[self.queue addOperation:[[ActionOperation alloc] initWithNode:nodeToMove action:move2]];
And because the queue is serial, you know that move2
will not be started until move1
is done.
Swift uninitialized let property
Well, the problem was caused by the way I crate group action:
let gravOperation = ActionOperation(node: piecesLayer, action: SKAction.group(groupActions))
When groupActions
array is empty action will never be executed on the node and if we call:
node.run(badGroupAction, completion: { /* never called */ })
the completion
will be never called. That's why queue was stuck. I think it's a SpriteKit
flaw. I would prefer completion
to be called immediately if we try to run empty group or sequence.
Why does action
become uninitialized
? I don't know, I think it's a bug, probably related to the empty array.
Related Topics
How to Implement Protocol Methods That Return Covariant Selfs
How to Stream Remote Audio in iOS 13? (Swiftui)
Fblpromises Framework Not Found
Firebase Uid VS Document-Id and Firestore Rules
Opposite of _Conversion in Swift to Assign to a Value of a Different Type
Multiple Enum Types List All Cases
Converting Url to String and Back Again
How to Find Max Value for Double and Float in Swift
Fastlane "Nokogiri Requires Ruby Version >= 2.3.0." Error
Why Does Adding 'Dynamic' Fix My Bad Access Issues
Monitoring App Switching on Os X
Mutating Function Inside Class
Swift & Firebase - How to Store More User Data Other Than Email and Password
How to Increase (Animate) the Width of the Square on Both Ends
How to Manage Swiftui State with Nested Structs
Use Multiple Codingkeys for a Single Property