1

I have 3 different NSTimers which I want to fire every 0.3 of a second, but I want the 3 NSTimers to be staggered so that they don't all fire at the same time. For example NSTimer1 fires at 0.1 and then at 0.4, NSTimer2 fires at 0.2 and then at 0.5 and NSTimer3 fires at 0.3 and then at 0.6 and so one.

Below is what I am using at the moment, I'm not sure if they do in fact fired at the same time, I only assume. Any advise would be appreciated.

var timer1 = NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: Selector("updateSegment1"), userInfo: nil, repeats: true)
var timer2 = NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: Selector("updateSegment2"), userInfo: nil, repeats: true)
var timer3 = NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: Selector("updateSegment3"), userInfo: nil, repeats: true)
Jarron
  • 981
  • 1
  • 11
  • 23
  • 2
    Instead of scheduling 3 NSTimers you should calculate the delta time from the update method and use a counter to determine how much time has passed. – Epic Byte Jun 23 '15 at 15:09
  • 1
    try dispatch_after http://stackoverflow.com/questions/24034544/dispatch-after-gcd-in-swift/24318861#24318861 – Will M. Jun 23 '15 at 15:22
  • I'm not familiar with dispatch_after, but am looking into it now. But basically, does this allow each section of code to run once the previous is completed? Because this may be better then scheduling each update as Epic said. – Jarron Jun 23 '15 at 15:34
  • you can use dispatch_after to delay when you fire your timers, so you can dispatch one so it fires after a delay of 0.1, dispatch another so it fires after a delay of 0.2 etc. – Will M. Jun 23 '15 at 15:38
  • You can create your timers without adding them to the runloop, and then in your dispatch_after blocks, add them to the runloop after a delay basically. That way you will have a reference to your timer that you can invalidate, but it will be schedules after a delay. – Will M. Jun 23 '15 at 15:39
  • Thanks Will. One more thing though, if I can. From what I've gathered, the dispatch_after and sangony answer should product the same result, is that correct? I'm just trying to figure out the benefits of using one or the other. Thanks again for your input. – Jarron Jun 23 '15 at 15:44
  • Sangony's answer uses SpriteKit, while dispatch_after is built in to the language. dispatch_after allows you to use the dispatch queue methods to pick the right queue for the actions you want to delay. – Will M. Jun 23 '15 at 15:50

2 Answers2

1

You can use blocks to accomplish this.

-(void)didMoveToView:(SKView *)view {

    SKAction *wait0 = [SKAction waitForDuration:0.1];
    SKAction *block0 = [SKAction runBlock:^{
        // run first timer code
    }];
    [self runAction:[SKAction sequence:@[wait0, block0]]];

    SKAction *wait1 = [SKAction waitForDuration:0.2];
    SKAction *block1 = [SKAction runBlock:^{
        // run second timer code
    }];
    [self runAction:[SKAction sequence:@[wait1, block1]]];

    SKAction *wait2 = [SKAction waitForDuration:0.3];
    SKAction *block2 = [SKAction runBlock:^{
        // run third timer code
    }];
    [self runAction:[SKAction sequence:@[wait2, block2]]];

}

If you are looking to use dispatch, try this code...

Create a property for your timer:

@property (nonatomic, strong) dispatch_source_t myTimer;

Next, create the timer:

// Get the queue to run the blocks on
dispatch_queue_t queue = dispatch_get_main_queue();

// Create a dispatch source, and make it into a timer that goes off every second
self.myTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.myTimer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0);

// When the timer goes off, run your code
dispatch_source_set_event_handler(self.myTimer, ^{
    //code...
});

// Dispatch sources start out paused, so start the timer by resuming it
dispatch_resume(self.myTimer);

// To cancel the timer, just set the timer variable to nil:
self.myTimer = nil;
sangony
  • 11,426
  • 4
  • 34
  • 51
  • Thanks Sangony, this looks great. I'm going to use this until I can figure out how dispatch_after works, or whether dispatch_after is what I require. – Jarron Jun 23 '15 at 15:36
  • @Jarron - Glad it helped. Just remember that all lines get executed at the same time. – sangony Jun 23 '15 at 15:39
  • Make sure you have a reference to the timer outside of the block otherwise you won't be able to invalidate it when you are done – Will M. Jun 23 '15 at 15:51
1

I suggest you use an SKAction or two instead of an NSTimer in Sprite Kit games because actions pause/resume appropriately when you pause/resume the SKView and/or SKScene. Here's an example of how to stagger events with SKActions:

override func didMoveToView(view:SKView) {

    let wait = SKAction.waitForDuration(0.1)
    let block1 = SKAction.runBlock({
        updateSegment1()
    })
    let block2 = SKAction.runBlock({
        updateSegment2()
    })
    let block3 = SKAction.runBlock({
        updateSegment3()
    })
    let sequence = SKAction.sequence([wait,block1,wait,block2,wait,block3])
    self.runAction(SKAction.repeatActionForever(sequence), withKey:"timer")

    // Use the following to terminate the timer
    //self.removeActionForKey("timer")
}
0x141E
  • 12,185
  • 2
  • 36
  • 51