【问题标题】:How to perform an action exactly after 0.3 seconds for a given number of times?如何在 0.3 秒后准确执行给定次数的动作?
【发布时间】:2016-04-18 12:23:47
【问题描述】:
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)

在 30 执行几乎一秒钟后,控制台开始表现怪异,同时显示两条和两条打印行

9.0 - 9.88806998729706 : 30
9.3 - 9.88832598924637 : 31

有没有办法让XCTest 更接近“在正确的时间”实际执行请求?就像得到应该在 9 秒后完成的请求在 9.88 秒后没有完成..

【问题讨论】:

  • 我删除了 XCTest 标签,因为它与 XCTests 没有任何特定关系。您需要这个来进行测试只是巧合。虽然......我很想看看你实际上想像这样测试什么(然后添加标签)。
  • @nhgrif 提到 XCTest 很重要,因为它实际上调用了 waitForExpectationsWithTimeout() - 我不确定这是否适用于例如阻塞测试中的主线程
  • 如果您觉得提及XCTest 很重要,那么您确实需要提供您尝试运行的测试以获取上下文。就目前而言,这有点像XY Problem
  • @nhgrif 是的,可能真的没什么大不了的。
  • 好像等了10秒,但至少要等12秒。

标签: swift grand-central-dispatch core-foundation


【解决方案1】:

不确定您是否打算使用 dispatch,但 NSTimer 在我的测试中表现得更加准确。这是一些运行动作 numOfTimes 次的示例代码

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()
    }
}

程序的一些输出:

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

它保持在预期时间的几毫秒内(我假设这是调用 CFAbsoluteTimeGetCurrent 所需的时间)并且不会漂移或聚集在一起。

这种方法的一个缺点是它不像在您的代码中那样预先安排每个操作,尽管我不确定这对您是否重要。

【讨论】:

  • NSTimer 的记录精度为 50-100 毫秒,请参阅 stackoverflow.com/questions/9737877/…,所以这已经足够了。
  • 很高兴知道。谢谢!
  • 另外,NSTimer 提供了一个容差值,允许您指定系统必须按时执行任务的严格程度。虽然默认值为 0,但即使实际精度稍有偏差,NSTimer 也会自动安排下一次触发日期,而不管上一次触发实际发生的时间,以防止拖动。
【解决方案2】:

使用CADisplayLink,一个高精度定时器

(不是很好的代码,只是快速组合在一起)

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)
    }
}

注意该方法实际上是多次执行的,在内部你必须检查是否有足够的时间过去。

【讨论】:

    猜你喜欢
    • 2012-05-04
    • 1970-01-01
    • 1970-01-01
    • 2020-01-12
    • 2015-04-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-04
    • 1970-01-01
    相关资源
    最近更新 更多