I want to set up a timer in -(void) main {} method of a NSOperation subclass. However, I the timer doesn't fire. I tried [[NSRunLoop currentRunLoop] addTimer:timer forMode: NSDefaultRunLoopMode]; with no success. Am I missing smth?
[EDIT] Will this run the timer on the main thread or on NSOperation's thread? (guessing NSOperation, but I'm not sure)
I found what need to be done to start the timer. I'll put it here if anyone ever needs it:
[[NSRunLoop currentRunLoop] run];
However, I'm still not sure if this will run in the NSOperation's thread.
I find that it is sometimes useful to wrap an NSOperation around a method that is fired by a timer, like you are doing, to take advantage of the dependencies, cancellation, and other features provided by the operations queue. It is however not usually necessary for the timer to run in a separate thread, nor is it usually necessary for the method that the timer invokes to be running in a background thread. In fact, most of the time it shouldn't run in a background thread because the method updates the UI. If this is true for you too a setup like this should work:
#import <Foundation/Foundation.h>
#interface TimerOperation : NSOperation {
#private
NSTimer* _timer;
}
#property (nonatomic, readonly) BOOL isExecuting;
#property (nonatomic, readonly) BOOL isFinished;
#end
(Add in your own state, custom constructor, etc.).
#implementation TimerOperation
#synthesize isExecuting = _executing;
#synthesize isFinished = _finished;
- (id) init {
if ((self = [super init])) {
_executing = NO;
_finished = NO;
}
return self;
}
- (void) dealloc {
[_timer release];
[super dealloc];
}
- (BOOL) isConcurrent {
return YES;
}
- (void) finish {
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
_executing = NO;
_finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
- (void) start {
if ([self isCancelled]) {
[self willChangeValueForKey:#"isFinished"];
_finished = YES;
[self didChangeValueForKey:#"isFinished"];
} else {
[self willChangeValueForKey:#"isExecuting"];
[self performSelectorOnMainThread:#selector(main)
withObject:nil
waitUntilDone:NO];
_executing = YES;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (void) timerFired:(NSTimer*)timer {
if (![self isCancelled]) {
// Whatever you want to do when the timer fires
}
}
- (void) main {
_timer = [[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timerFired:)
userInfo:nil
repeats:YES] retain];
}
- (void) cancel {
[_timer invalidate];
[super cancel];
}
#end
Of course, if the main thread is blocked (for instance, because of a long database save) you will not see the timer firing, but most of the time this works fine.
You can use the same setup to deal with asynchronous APIs inside NSOperations.
Related
I listen to touch and add SKAction to a sprite. If existing actions are not complete yet, I want the action to be added to a queue so it will execute one after another.
Any experienced similar design?
I did using Array and Block. If there is any easier approach?
#interface GAPMyScene()
#property(strong,nonatomic)SKSpriteNode*ufo;
#property(strong,nonatomic)NSMutableArray*animationQueue;
#property(copy,nonatomic)void(^completeMove)();
#end
#implementation GAPMyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.ufo = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
self.animationQueue = [[NSMutableArray alloc] init];
__unsafe_unretained typeof(self) weakSelf = self;
self.completeMove = ^(void){
[weakSelf.ufo runAction:[SKAction sequence:[weakSelf.animationQueue copy]] completion:weakSelf.completeMove];
NSLog(#"removeing %#", weakSelf.animationQueue);
[weakSelf.animationQueue removeAllObjects];
};
[self addChild:self.ufo];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKAction*moveAnimation = [SKAction moveTo:location duration:2];
if (![self.ufo hasActions]) {
[self.ufo runAction:moveAnimation completion:self.completeMove];
} else {
[self.animationQueue addObject:moveAnimation];
NSLog(#"in queue %#", self.animationQueue);
}
}
}
#end
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.
This question already has answers here:
Stop and Reset NSTimer
(2 answers)
Closed 9 years ago.
I have created a NSTimer that simply times minutes and I am wishing to add a stop and reset button to it. So far my code looks like this:
#implementation TimeController
int timeTick = 0;
NSTimer *timer;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
labelTime.text = #"0";
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)startTimer:(id)sender {
[timer invalidate];
timer= [NSTimer scheduledTimerWithTimeInterval:60.0 target:(self) selector:(#selector(tick)) userInfo:(nil) repeats:(YES)];
}
-(void)tick{
timeTick++;
NSString *timeString = [[NSString alloc] initWithFormat:#"%d", timeTick];
labelTime.text = timeString;
}
#end
Thanks in advance!
Your timeTick and timer are actually globals which is probably not what you intended. You should probably declare them as instance variables. This will allow you to have multiple instances of your TimeController and have them all count independently.
Then your code may look something like this
#interface TimeController ()
#property (nonatomic, assign) NSInteger minutes;
#property (nonatomic, strong) NSTimer *timer;
#end
#implementation TimeController
- (void)viewDidLoad
{
[super viewDidLoad];
[self updateMinuteLabel];
}
- (IBAction)startTimer
{
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:60.0
target:self
selector:#selector(tick)
userInfo:nil
repeats:YES];
}
- (IBAction)stopTimer
{
[self.timer invalidate];
}
- (IBAction)resetTimer
{
self.minutes = 0;
[self updateMinuteLabel];
}
- (void)tick
{
self.minutes += 1;
[self updateMinuteLabel];
}
- (void)updateMinuteLabel
{
self.minuteLabel.text = [NSString stringWithFormat:#"%d", self.minutes];
}
#end
I have an image view that has two animation images, occuring in 1-second intervals. I want to run some methods when my image view is displaying one of those two images
I already tried doing:
if(self.imageViewThatPerformsAnimation.image == [UIImage imageNamed: #"someImage"])
[self doSomeMethod];
but when I tried this and ran it, [self doSomeMethod]; always ran, and not just when the image view was displaying that one image.
I'm thinking about having a timer that changes a boolean value every one second then saying
if (booleanValue==YES)
[self doSomeMethod]
It's just that I feel there may be a better way.
If you wanted to use a NSTimer, it might look like:
#interface MyViewController ()
{
NSTimer *_timer;
NSArray *_images;
NSInteger _currentImageIndex;
}
#end
#implementation MyViewController
#synthesize imageview = _imageview;
- (void)viewDidLoad
{
[super viewDidLoad];
_images = [NSArray arrayWithObjects:
[UIImage imageNamed:#"imgres-1.jpg"],
[UIImage imageNamed:#"imgres-2.jpg"],
[UIImage imageNamed:#"imgres-3.jpg"],
[UIImage imageNamed:#"imgres-4.jpg"],
nil];
_currentImageIndex = -1;
[self changeImage];
// Do any additional setup after loading the view.
}
- (void)changeImage
{
_currentImageIndex++;
if (_currentImageIndex >= [_images count])
_currentImageIndex = 0;
self.imageview.image = [_images objectAtIndex:_currentImageIndex];
if (_currentImageIndex == 0)
[self doSomething];
}
- (void)startTimer
{
if (_timer) {
[_timer invalidate];
_timer = nil;
}
_timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(changeImage)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
}
- (void)stopTimer
{
[_timer invalidate];
_timer = nil;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self startTimer];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self stopTimer];
}
I have an NSOperation subclass that I want to run concurrently.
My understanding is that for concurrent operations to work:
I need to define isConcurrent to return YES.
I need to define the start method
I need to send KVOs notification for isExecuting and isFinished when it's done.
Using #synthesize will automatically send the appropriate KVO notifications when the values for isExecuting and isFinished are changed.
Despite this, I have verified that my queue never moves on to the next item.
Here's the meat of my code:
#interface MyOperation()
#property (readwrite) BOOL isExecuting;
#property (readwrite) BOOL isFinished;
#end
#implementation MyOperation
- (void)start
{
#autoreleasepool {
self.isExecuting = YES;
self.HTTPOperation = [[AFHTTPRequestOperation alloc] initWithRequest: URLRequest];
_HTTPOperation.completionBlock = [^{
[self completed];
self.isExecuting = NO;
self.isFinished = YES;
} copy];
[_HTTPOperation start];
}
}
- (BOOL)isConcurrent
{
return YES;
}
- (void)completed
{
}
#end
What am I missing?
(This is on an iPhone, but I can't imagine that matters.)
It looks like whatever KVO notifications #synthesize sends aren't enough for NSOperationQueue to move on.
Sending the notifications manually fixes the problem:
- (void)start
{
#autoreleasepool {
[self willChangeValueForKey:#"isExecuting"];
self.isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
NSURLRequest *URLRequest = [self buildRequest];
if (!URLRequest) {
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
return;
}
self.HTTPOperation = [[AFHTTPRequestOperation alloc] initWithRequest: URLRequest];
_HTTPOperation.completionBlock = [^{
[self completed];
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
} copy];
[_HTTPOperation start];
}
}
See also:
Why does NSOperation disable automatic key-value observing?
What's your "queue" look like? Are you using an NSOperationQueue?
Anyway, I'll try to answer your question with what I understood :P
I would create a delegate for my NSOperation and have KVO take care of calling this.
Say for example your NSOperation class looks like this
#interface MyOperation : NSOperation
#property (assign) id<MyOperationDelegate> delegate;
Your implementation
#synthesize delegate;
#synthesize error;
-(id)init{
self = [super init];
if(self){
[self addObserver:self forKeyPath:#"isFinished"
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
-(void)dealloc{
[self removeObserver:self forKeyPath:#"isFinished"];
[super dealloc];
}
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if([keyPath isEqualToString:#"isFinished"] == YES){
if([self isCancelled] == NO){
if(delegate != nil && [delegate respondsToSelector:#selector(operationComplete:)]){
[delegate taskComplete:self];
}
}else{
if(delegate != nil && [delegate respondsToSelector:#selector(operationCancelled)]){
[delegate taskCancelled];
}
}
}
}
-(void)main{
[NSException exceptionWithName:kTaskException
reason:#"Only to be used with subclass"
userInfo:nil];
}
And finally your protocol
#class MyOperation;
#protocol MyOperationDelegate <NSObject>
#optional
-(void)operationComplete:(MyOperation*)operation;
-(void)operationCancelled;
What is the equivalent of these operations on ios3
[NSOperationQueue mainQueue];
[NSOperationQueue currentQueue];
There really wasn't an equivalent for +currentQueue. For +mainQueue you'd call
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
with a method that contained the work that needed to be done on the main thread.
There is no other alternative other than rolling your own.
Something like this might work: (untested)
#interface NSOperationQueue(MainQueueAdditions)
+ (NSOperationQueue *) mainQueue;
#end
#implementation NSOperationQueue(MainQueueAdditions)
+ (NSOperationQueue *) mainQueue {
static NSOperationQueue *queue = nil;
if(queue == nil) queue = [[NSMainOperationQueue alloc] init];
return queue;
}
#end
#interface NSMainOperationQueue : NSOperationQueue {}
#end
#implementation NSMainOperationQueue
- (void) addOperation:(NSOperation *) operation {
[self queueOperationInternal:operation];
}
- (void) addOperationWithBlock:(void (^)(void))block {
[self queueOperationInternal:[NSBlockOperation blockOperationWithBlock:block]];
}
- (void) queueOperationInternal:(NSOperation *) operation {
[[NSRunLoop mainRunLoop] performSelector:#selector(start) target:operation argument:nil order:-[operation queuePriority] modes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}
#end