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..
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With