这个 _dispatch_xref_dispose 错误是什么意思?

What does this _dispatch_xref_dispose error mean?

这是 的下一章。我有这个简化的 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
....

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

真正导致代码崩溃的是系统试图处理事件源。您可以通过使用您的 class 连续两次调用 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)

我建议这样做是因为零的数量很容易出错,使用常量有助于 reader 理解代码的实际作用。