这是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
,但我还没有找到一个让它消失的实现。
答案 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)
我建议这样做,因为零的数量容易出错,使用常量可以帮助读者理解代码实际上在做什么。