1

Here's a struct I've written to convert an NSTimeInterval into a walltime-based dispatch_time_t:

public struct WallTimeKeeper {

    public static func walltimeFrom(spec: timespec)->dispatch_time_t {
        var mutableSpec = spec
        let wallTime = dispatch_walltime(&mutableSpec, 0)
        return wallTime
    }

    public static func timeStructFrom(interval: NSTimeInterval)->timespec {
        let nowWholeSecsFloor = floor(interval)
        let nowNanosOnly = interval - nowWholeSecsFloor
        let nowNanosFloor = floor(nowNanosOnly * Double(NSEC_PER_SEC))
        println("walltimekeeper: DEBUG: nowNanosFloor:        \(nowNanosFloor)")
        var thisStruct = timespec(tv_sec: Int(nowWholeSecsFloor),
            tv_nsec: Int(nowNanosFloor))
        return thisStruct
    }
}

I've been trying to test the accuracy of it in a Playground, but my results are confusing me.

Here's the code in my Playground (with my WallTimeKeeper in the Sources folder):

var stop = false
var callbackInterval: NSTimeInterval?
var intendedTime: NSDate?
var intendedAction: ()->() = {}

func testDispatchingIn(thisManySeconds: NSTimeInterval){
    intendedTime = NSDate(timeIntervalSinceNow: thisManySeconds)
    intendedAction = stopAndGetDate
    dispatchActionAtDate()
    loopUntilAfterIntendedTime()
    let success = trueIfActionFiredPunctually() //always returns false
}

func dispatchActionAtDate(){
    let timeToAct = dateAsDispatch(intendedTime!)
    let now = dateAsDispatch(NSDate())
    /*****************
    NOTE: if you run this code in a Playground, comparing the above two
    values will show that WallTimeKeeper is returning times the
    correct number of seconds apart.
    ******************/
    dispatch_after(timeToAct, dispatch_get_main_queue(), intendedAction)
}

func loopUntilAfterIntendedTime() {
    let afterIntendedTime = intendedTime!.dateByAddingTimeInterval(1)
    while stop == false && intendedTime?.timeIntervalSinceNow > 0 {
        NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode,
            beforeDate: afterIntendedTime)
    }
}

func trueIfActionFiredPunctually()->Bool{
    let intendedInterval = intendedTime?.timeIntervalSinceReferenceDate
    let difference = intendedInterval! - callbackInterval!
    let trueIfHappenedWithinOneSecondOfIntendedTime = abs(difference) < 1
    return trueIfHappenedWithinOneSecondOfIntendedTime
}

func dateAsDispatch(date: NSDate)->dispatch_time_t{
    let intendedAsInterval = date.timeIntervalSinceReferenceDate
    let intendedAsStruct = WallTimeKeeper.timeStructFrom(intendedAsInterval)
    let intendedAsDispatch = WallTimeKeeper.walltimeFrom(intendedAsStruct)
    return intendedAsDispatch
}

func stopAndGetDate() {
    callbackInterval = NSDate().timeIntervalSinceReferenceDate
    stop = true
}

testDispatchingIn(3)

...so not only doestrueIfActionFiredPunctually() always returns false, but the difference value--intended to measure the difference between the time the callback fired and the time it was supposed to fire--which in a successful result should be really close to 0, and certainly under 1--instead comes out to be almost exactly the same as the amount of time the callback was supposed to wait to fire.

In summary: an amount of time to wait is defined, and an action is set to fire after that amount of time. When the action fires, it creates a timestamp of the moment it fired. When the timestamp is compared to the value it should be, instead of getting close to zero, we get close to the amount of time we were supposed to wait.

In other words, it appears as if the action passed to dispatch_after is firing immediately, which it absolutely shouldn't!

Is this something wrong with Playgrounds or wrong with my code?

EDIT: It's the code. Running the same code inside a live app gives the same result. What am I doing wrong?

Le Mot Juiced
  • 3,129
  • 1
  • 20
  • 38
  • `abs(difference) < 0` will never be true :) – Martin R Jul 25 '15 at 17:28
  • D'oh! You're right of course. Changed to `abs(difference) < 1`. Results the same, though--except this time for the right reasons. – Le Mot Juiced – Le Mot Juiced Jul 25 '15 at 17:51
  • Have you taken a look at http://stackoverflow.com/questions/24058336/how-do-i-run-asynchronous-callbacks-in-playground ? – luk2302 Jul 25 '15 at 17:56
  • Yes, basically. I've thought along the same lines, but it turns out not to be relevant. For those not interested in following a blind link: the post covers activating `XCPSetExecutionShouldContinueIndefinitely(continueIndefinitely: true)` for the purpose of testing asynchronous function calls. Unfortunately, in this case, activating it has no effect on the Playground's result. The fact is that the dispatch seems to be firing *instantly*, and so allowing execution to continue indefinitely has no effect. – Le Mot Juiced Jul 25 '15 at 18:00
  • Well that was an excellent question Martin. It still doesn't work as expected. I'll be! Editing post now. – Le Mot Juiced Jul 25 '15 at 18:50

1 Answers1

1

I figured it out. It's a head-smacker. I'll leave it up in case anyone is having the same problem.

I was using NSDate().timeIntervalSinceReferenceDate to set my walltimes.

Walltimes require NSDate().timeIntervalSince1970!

The dispatch_after tasks all fired instantly because they thought they were scheduled for over forty years ago!

Changing everything to NSDate().timeIntervalSince1970 makes it work perfectly.

Moral: don't use walltimes unless you're sure your reference date is 1970!

Le Mot Juiced
  • 3,129
  • 1
  • 20
  • 38
  • More precisely, a `struct timespec` is based in the Unix time (which is what `timeIntervalSince1970` returns). – Martin R Jul 25 '15 at 19:47