Почему этот dispatch_after срабатывает мгновенно?

Вот структура, которую я написал для преобразования NSTimeInterval в 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
    }
}

Я пытался проверить точность на игровой площадке, но мои результаты меня смущают.

Вот код в моей Playground (с моим WallTimeKeeper в папке Sources):

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)

... так что не только dotrueIfActionFiredPunctually() всегда возвращает false, но и значение difference, предназначенное для измерения разницы между временем срабатывания обратного вызова и временем, когда он должен был должен срабатывать, что в случае успешного результата должен быть очень близок к 0 и уж точно меньше 1 — вместо этого получается почти то же самое, что и количество времени, в течение которого обратный вызов должен был ждать срабатывания.

Подводя итог: определяется количество времени ожидания, и действие запускается по истечении этого времени. Когда действие запускается, оно создает временную метку момента запуска. Когда метка времени сравнивается со значением, которым она должна быть, вместо того, чтобы приближаться к нулю, мы приближаемся к количеству времени, которое мы должны были ждать.

Другими словами, создается впечатление, что действие, переданное dispatch_after, срабатывает немедленно, чего абсолютно не должно быть!

Это что-то не так с Playgrounds или с моим кодом?

EDIT: Это код. Запуск того же кода внутри живого приложения дает тот же результат. Что я делаю не так?


person Le Mot Juiced    schedule 25.07.2015    source источник
comment
abs(difference) < 0 никогда не будет правдой :)   -  person Martin R    schedule 25.07.2015
comment
О! Вы правы, конечно. Изменено на abs(difference) < 1. Результаты те же самые, за исключением того, что на этот раз по правильным причинам. - Ле Мот Сок   -  person Le Mot Juiced    schedule 25.07.2015
comment
Вы взглянули на stackoverflow.com/questions/24058336/ ?   -  person luk2302    schedule 25.07.2015
comment
Да, в основном. Я думал в том же духе, но это оказалось неактуальным. Для тех, кто не заинтересован в переходе по слепой ссылке: пост посвящен активации XCPSetExecutionShouldContinueIndefinitely(continueIndefinitely: true) с целью тестирования вызовов асинхронных функций. К сожалению, в этом случае его активация никак не влияет на результат Playground. Дело в том, что диспетчеризация срабатывает мгновенно, поэтому бесконечное продолжение выполнения не имеет никакого эффекта.   -  person Le Mot Juiced    schedule 25.07.2015
comment
Что ж, это был отличный вопрос, Мартин. Это все еще не работает, как ожидалось. Я буду! Редактирую пост сейчас.   -  person Le Mot Juiced    schedule 25.07.2015


Ответы (1)


Я понял. Это головокружение. Я оставлю это, если у кого-то есть такая же проблема.

Я использовал NSDate().timeIntervalSinceReferenceDate для установки времени стены.

Настенное время требует NSDate().timeIntervalSince1970!

Все задачи dispatch_after запускались мгновенно, потому что они думали, что они были запланированы более сорока лет назад!

Изменение всего на NSDate().timeIntervalSince1970 заставляет его работать отлично.

Мораль: не используйте время на стене, если вы не уверены, что ваша отсчетная дата — 1970 год!

person Le Mot Juiced    schedule 25.07.2015
comment
Точнее, struct timespec основано на времени Unix (именно это и возвращает timeIntervalSince1970). - person Martin R; 25.07.2015