Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: can I add and subtract `dispatch_time_t` variables?

I need to do some time math in iOS, with Swift.

I have to use dispatch_walltime. I hope that can be taken as axiomatic. Where time math is concerned, I think I'm likely to get the response "just use NSDate," but please take it on faith: I am bound to dispatch_walltime.

Now, it's plain why someone might suggest NSDate, because when you're using NSTimeInterval and NSDate and that good stuff, it's pretty easy to make custom timestamps and compare them and do all kinds of time math.

But I have to use dispatch_time_t, and specifically dispatch_walltime created like this:

//Get the timeInterval of now.
let nowInterval = NSDate().timeIntervalSince1970

//Make a timespec from it.
var nowStruct = timespec(tv_sec: Int(nowInterval), tv_nsec: 0)

//Make a walltime definition from that.
let referenceWalltime = dispatch_walltime(&nowStruct, 0)

Later on I need to use that reference time in various ways. For instance, I need to get the time interval between the reference time and whatever time it happens to be.

I am attempting to do this the same way I would with NSTimeInterval, in other words, make a new one and subtract the old one from it:

//Repeat everything from before to make a new wall time.
let newNowInterval = NSDate().timeIntervalSince1970
var newNowStruct = timespec(tv_sec: Int(newNowInterval), tv_nsec: 0)
let newWalltime = dispatch_walltime(& newNowStruct, 0)

//Time math a la NSTimeInterval to find the interval:
let walltimeInterval = newWalltime - referenceWalltime

Will that work?

like image 749
Le Mot Juiced Avatar asked Nov 01 '22 05:11

Le Mot Juiced


1 Answers

The short answer is: no. That code will crash.

The better answer is: no, but it can be done, and it's not all that different in the end.

I did some investigating on my own in a Playground and learned some interesting things, and I believe I figured out the right way to do this.

I'm pasting the entirety of my Playground here, so that others can copy-paste it and figure out how to do their own dispatch_time math.

Comments marked by //********* in the code denote the key things I learned.

import UIKit
import XCPlayground
XCPSetExecutionShouldContinueIndefinitely(continueIndefinitely: true)

public class IntervalMaker {

    var referenceWalltime: dispatch_time_t = 0
    var newWalltime: dispatch_time_t = 0
    var walltimeInterval: dispatch_time_t = 0

    func scheduleWalltimeSequence () {
         let threeSeconds = Int64(NSEC_PER_SEC) * 3
         let now = walltimeNow()
         let dispatchTimeInThree = dispatch_time(now, threeSeconds)
         let dispatchTimeInSix = dispatch_time(now,
             2 * threeSeconds)
         setReferenceWalltimeToNow()
         dispatch_after(dispatchTimeInThree, dispatch_get_main_queue(),
             setNewWalltimeToNow)
         dispatch_after(dispatchTimeInSix,
             dispatch_get_main_queue(), dispatchBasedOnDispatchMath)
    }

    func walltimeNow()->dispatch_time_t{
         let nowInterval = NSDate().timeIntervalSince1970
         var nowStruct = timespec(tv_sec: Int(nowInterval), tv_nsec: 0)
         return dispatch_walltime(&nowStruct, 0)
    }

    func setReferenceWalltimeToNow () {
         referenceWalltime = walltimeNow()
    }

    func setNewWalltimeToNow (){
         newWalltime = walltimeNow()
    }

    func dispatchBasedOnDispatchMath () {
         computeInterval() //Should be three seconds
         let nineTheWrongWay = referenceWalltime + (walltimeInterval * 3)
         let nineTheRightWay = dispatch_time(referenceWalltime,
             Int64(walltimeInterval) * 3)
         dispatch_after(nineTheWrongWay,
             dispatch_get_main_queue(), finalPrintln)
//********** THE ABOVE DOES NOT WORK CORRECTLY - prints 6 seconds later
         dispatch_after(nineTheRightWay,
             dispatch_get_main_queue(), finalPrintln)
//********** THE ABOVE WORKS CORRECTLY - prints 9 seconds later
    }

    func finalPrintln () {
        let now = walltimeNow()
        println("I should be printing nine seconds from reference time.")
        println("It's actually \(referenceWalltime - now) nanoseconds after")
    }

    func computeInterval () {
        walltimeInterval = referenceWalltime - newWalltime
//********** dispatch_walltimes actually count backwards, and *CANNOT* be
//********** negative: writing `newWalltime - referenceWalltime` will crash
    }
}

 let intervaller = IntervalMaker()
 intervaller.scheduleWalltimeSequence()
like image 71
Le Mot Juiced Avatar answered Nov 15 '22 07:11

Le Mot Juiced