这个 _dispatch_xref_dispose 错误是什么意思?

Tra*_*ggs 2 timer grand-central-dispatch swift

这是我不久前问过的一个问题的下一章。我有这个简化的计时器,它的灵感来自于 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
Run Code Online (Sandbox Code Playgroud)

问题在于我将新计时器分配给同一个变量。基本用例是我用完了一个计时器,所以我停止它,但随后可能想创建一个新计时器并启动它。

在操场上,该标记线给出了错误:

Execution was interrupted, reason: EXC_BAD_INSTRUCTION {code=EXC_I386_INVOP, subcode=0x0).
Run Code Online (Sandbox Code Playgroud)

如果我在 iOS 应用程序中执行类似的操作,我会得到如下所示的异常跟踪:

0: _dispatch_xref_dispose
5: MyViewController.scanTimer.setter
....
Run Code Online (Sandbox Code Playgroud)

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

Sco*_*son 5

实际上导致代码崩溃的是当系统尝试处理事件源时。您可以start()通过使用您的类连续调用两次来看到这一点。在第二次调用期间,您将看到应用程序在尝试创建新事件源并将其分配给字段时崩溃source

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

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

话虽如此,您的代码中并不清楚为什么您会调用dispatch_suspend而不是dispatch_source_cancel. 当您调用stop()计时器时,您就完成了调度源。如果您再次调用start(),您无论如何都会得到一个全新的事件源。我建议将您的stop()功能更改为:

func stop() {
    if self.source != nil {
        dispatch_source_cancel(self.source)
        self.source = nil
    }
}
Run Code Online (Sandbox Code Playgroud)

这还有解决崩溃问题的额外好处。

如果您愿意接受建议,我还建议您将硬编码常量替换为调度库符号常量,表示一秒内的纳秒数:

dispatch_source_set_timer(source, DISPATCH_TIME_NOW,
    UInt64(self.interval) * NSEC_PER_SEC, 0)
Run Code Online (Sandbox Code Playgroud)

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