Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to perform an action exactly after 0.3 seconds for a given number of times?

let expecation = expectationWithDescription("do tasks")
for i in 0...40 {

    let afterTiming = 0.3 * Double(i)
    let startTime = CFAbsoluteTimeGetCurrent()

    let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(afterTiming * Double(NSEC_PER_SEC)))

    dispatch_after(delayTime, dispatch_get_main_queue()) {
        let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
        print("\(afterTiming) - \(timeElapsed) : \(i)")
    }
}

waitForExpectationWithTimeout(14)

after 30 executes its almost a second off, and console start acting weird with showing two and two print lines simultaneously

9.0 - 9.88806998729706 : 30
9.3 - 9.88832598924637 : 31

Is there any way for an XCTest to get closer to actually doing the requests "on correct time"? Like getting the request that should be done after 9 seconds not being done after 9.88 seconds..

like image 604
bogen Avatar asked Apr 18 '16 12:04

bogen


2 Answers

Not sure if you're set on using dispatch, but NSTimer is behaving much more accurately in my testing. Here's some sample code to run an action numOfTimes times

var iterationNumber = 0
var startTime = CFAbsoluteTimeGetCurrent()
let numOfTimes = 30

// Start a repeating timer that calls 'action' every 0.3 seconds 
var timer = NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: #selector(ViewController.action), userInfo: nil, repeats: true)

func action() {
    // Print relevant information
    let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
    print("\(Double(iterationNumber+1) * 0.3) - \(timeElapsed) : \(CFAbsoluteTimeGetCurrent())")

    // Increment iteration number and check if we've hit the limit
    iterationNumber += 1
    if iterationNumber > numOfTimes {
        timer.invalidate()
    }
}

Some of the output of the program:

7.8 - 7.80285203456879 : 25
8.1 - 8.10285001993179 : 26
8.4 - 8.40283703804016 : 27
8.7 - 8.70284104347229 : 28
9.0 - 9.00275802612305 : 29
9.3 - 9.3028250336647 : 30

It stays within a few milliseconds of the expected time (I'm assuming that's the time it takes to call CFAbsoluteTimeGetCurrent) and doesn't drift or clump them together.

One disadvantage to this approach is that it doesn't schedule each action all up front like it does in your code, although I'm not sure if that matters to you.

like image 183
Bernem Avatar answered Oct 08 '22 21:10

Bernem


Using CADisplayLink, a high precision timer

(not a nice code, just hacked together fast)

var displayLink: CADisplayLink?
var startTime: CFAbsoluteTime = 0
var nextTime: CFAbsoluteTime = 0
var index: Int = 0

func testIncrement() {
    self.startTime = CFAbsoluteTimeGetCurrent()
    self.nextTime = self.startTime

    displayLink = CADisplayLink(target: self, selector: #selector(execute))
    displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)

    let expectation = expectationWithDescription("test")

    self.waitForExpectationsWithTimeout(20.0, handler: nil)
}

func execute() {
    let currentTime = CFAbsoluteTimeGetCurrent()

    if (currentTime - nextTime < 0) {
        return
    }

    let timeElapsed = currentTime - startTime

    print("\(timeElapsed) : \(index)")

    index += 1
    nextTime = startTime + 0.3 * CFAbsoluteTime(index)

    if (index > 30) {
        displayLink?.removeFromRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
    }
}

Note the method is actually executed multiple times and internally you have to check whether enough time elapsed.

like image 32
Sulthan Avatar answered Oct 08 '22 19:10

Sulthan