1

I am having a bit of trouble using / implementing NSTimer into code. So far, i have this:

//Delay function from http://stackoverflow.com/questions/24034544/dispatch-after-gcd-in-swift/24318861#24318861
func delay(delay:Double, closure:()->()) {
    dispatch_after(
        //adds block for execution at a specific time
        dispatch_time(
            //creates a dispatch time relative to default clock
            DISPATCH_TIME_NOW,
            //Indicates that something needs to happen imediately
            Int64(delay * Double(NSEC_PER_SEC))
            //a 64bit decleration that holds the delay time times by the amount of nanoseconds in one seconds, inorder to turn the 'delay' input into seconds format
        ),
        dispatch_get_main_queue(), closure)
}


@IBAction func computerTurn(){

    if(isFirstLevel){levelLabel.text = ("Level 1"); isFirstLevel = false}
    else{ level++ }

    var gameOrderCopy = gameOrder
    var randomNumber = Int(arc4random_uniform(4))
    gameOrder.append(randomNumber)
    var i = 0
    var delayTime = Double(1)

    println(Double(NSEC_PER_SEC))
    for number in self.gameOrder{


        if number == 0{
            delay(delayTime++){self.greenButton.highlighted = true}
            self.delay(delayTime++){
                self.greenButton.highlighted = false
            }
        }

        else if number == 1{
            delay(delayTime++){self.redButton.highlighted = true}
             self.delay(delayTime++){
                self.redButton.highlighted = false
            }

        }

        else if number == 2{
            delay(delayTime++){self.yellowButton.highlighted = true}
             self.delay(delayTime++){
                self.yellowButton.highlighted = false
            }
        }
        else if number == 3{
            delay(delayTime++){self.blueButton.highlighted = true}
             self.delay(delayTime++){
                self.blueButton.highlighted = false
            }
        }
        println(delayTime)


    }
}

What i need to do, is replace the timer function, or get rid of it, and do the same as whats happening here, but using NSTimer.

Thanks

JohnnyCash
  • 67
  • 1
  • 5
  • Why? `dispatch_after()` is a much better tool for this problem. `NSTimer` would be needlessly complicated. – Rob Napier Mar 30 '15 at 02:55
  • @RobNapier, once the delayTime variable gets above 13, the _dispatch_after()_ glitches out as the time gets too long for the delay, so i need to use NSTimer so it can go above 13 seconds. – JohnnyCash Mar 30 '15 at 03:36
  • 1
    @RobNapier In iOS 8 (but not iOS 7), if you schedule a bunch of `dispatch_after`, like the code above does, (say, 20 of them, each 1 second apart), the first few are fine, but you'll see it start to coalesce some of the latter ones into groups. You have to use a repeating timer. If you want to use `dispatch_after`, you can't schedule them all up front like this code does, but rather schedule only the first one and have the completion block trigger the next one. – Rob Mar 30 '15 at 05:16
  • @JohnnyCash Again, please tell us if you're doing this for iOS or Mac OS X. – Rob Mar 30 '15 at 05:24

2 Answers2

2

NSTimer can be a little clunky in this context because it requires using either target-action or NSInvocation. However, NSTimer is toll-free bridged with CFRunLoopTimer, which you can call with a block:

func delay(delay:Double, closure:()->()) {
    let fireDate = delay + CFAbsoluteTimeGetCurrent()
    let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, fireDate, 0, 0, 0) { _ in
        closure()
    }
    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes)
}
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
  • I would have assumed that this would exhibit the same coalescing behavior that `dispatch_after` does, but it doesn't. Excellent. – Rob Mar 30 '15 at 05:47
  • 1
    That coalescing behavior is part of GCD and happens with custom events (i.e. anything that goes through `dispatch_async`). `CFRunLoop`/`NSTimer` predate GCD (by a lot) so they don't have that behavior. Incidentally, if you want to use GCD for this you can create a timer instead of a custom event with `dispatch_source_create` and `dispatch_source_set_timer`. – Nate Cook Mar 30 '15 at 14:49
  • 1
    Yep. And if you use dispatch timer and want to ensure that there's no coalescing taking place (esp with App Nap on Mac OS X), you'd specify `DISPATCH_TIMER_STRICT` as third parameter to `dispatch_source_set_timer`. (I know you probably know that Nate, but I mention that for future readers.) – Rob Mar 30 '15 at 17:13
  • 1
    @JohnnyCash: The first line gets the current time + the delay, since `CFRunLoopTimer` needs the actual time to execute, not an offset like `NSTimer`. The last two lines basically add up to calling `NSTimer.scheduledTimerWithTimeInterval(...)` but with a closure instead of target & selector or invocation. The [CFRunLoopTimer docs](https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFRunLoopTimerRef/) for those two functions should explain the rest. – Nate Cook Mar 31 '15 at 03:49
1

I would suggest:

  • Setting up some class property to maintain the "current" numeric index (which you'd start at 0);

  • Start your repeating timer;

  • The timer's handler would use the index to figure out which button to change and then do one "turn highlight on" and then do a dispatch_after for the "turn highlight back off";

    Then, this handler would look at the index and determine if you're at the end of the list or not. If you are, then cancel the timer and you're done. If you're not at the end of the list, then increment the "current index" and wait for the next scheduled timer fire off.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044