这个_dispatch_xref_dispose错误是什么意思?

时间:2016-03-29 22:51:33

标签: swift timer grand-central-dispatch

这是a question I asked a while back的下一章。我有这个简化的Timer,它的灵感来自Matt Neuburg的书中的Timer对象。

import Foundation
import XCPlayground

class Timer {
    private var queue = dispatch_queue_create("timer", nil)
    private var source: dispatch_source_t!
    var tick:()->() = {}
    var interval:NSTimeInterval = 1

    func start() {
        self.stop()
        self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue)
        dispatch_source_set_timer(source, DISPATCH_TIME_NOW, UInt64(self.interval) * 1000000000, 0)
        dispatch_source_set_event_handler(source, self.tick)
        dispatch_resume(self.source)
    }

    func stop() {
        if self.source != nil {
            dispatch_suspend(self.source)
        }
    }

}

var timer = Timer()
timer.interval = 5
timer.tick = { print("now \(NSDate())") }
timer.start()

timer.stop()
timer = Timer() // <<--BAD STUFF HAPPENS HERE

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

问题在于我将新计时器分配给同一个变量。基本用例是我已经完成了计时器,所以我停止了它,但是也许想要创建一个新计时器并启动它。

在游乐场,标记的行会出错:

Execution was interrupted, reason: EXC_BAD_INSTRUCTION {code=EXC_I386_INVOP, subcode=0x0).

如果我在iOS应用程序中执行类似的操作,我会得到一个类似于以下内容的异常跟踪:

0: _dispatch_xref_dispose
5: MyViewController.scanTimer.setter
....

如果它是结构或类似乎并不重要。同样的事情发生了我想知道我是否需要一个deinit,但我还没有找到一个让它消失的实现。

1 个答案:

答案 0 :(得分:3)

实际导致代码崩溃的原因是系统尝试处理事件源。您可以使用您的班级连续两次致电start()来查看此内容。在第二次调用期间,您将看到应用程序崩溃,尝试创建新的事件源并将其分配给source字段。

如果您没有为事件源调用dispatch_suspend,那么崩溃也永远不会发生。如果在替换计时器之前注释掉stop()的调用,则可以在示例中看到这一点。

我无法解释这种行为。我不知道为什么在处理事件源时调用dispatch_suspend会导致崩溃。在我看来,你应该向Apple报告一个错误。

话虽如此,您的代码中不清楚为什么要拨打dispatch_suspend而不是dispatch_source_cancel。当您在计时器上呼叫stop()时,您已完成调度源。如果您再次致电start(),您无论如何都会得到一个全新的活动来源。我建议您将stop()功能更改为:

func stop() {
    if self.source != nil {
        dispatch_source_cancel(self.source)
        self.source = nil
    }
}

这有助于解决崩溃问题。

如果您接受建议,我还建议您使用调度库符号常量替换您的硬编码常量,以获得秒内的纳秒数:

dispatch_source_set_timer(source, DISPATCH_TIME_NOW,
    UInt64(self.interval) * NSEC_PER_SEC, 0)

我建议这样做,因为零的数量容易出错,使用常量可以帮助读者理解代码实际上在做什么。