循环计时器不会按照说明使失效



我构建了一个"金属探测器"类型的应用程序,每 2 秒运行一次"calculateDistance"函数,该函数以米为单位计算用户位置和设置标记之间的距离,并将其设置为 var globalDistance。为简单起见,根据该距离是>=10 米还是<10 米,我正在播放一个调用"audioplayer"函数的计划计时器,该功能每 2 秒(如果距离>=10m(或每 0.5 秒(如果距离 <10m(播放一次"哔哔"声。

问题是,计时器永远不会像我指示的那样失效。因此,如果我使用设备从 <10m 移动到>10m,0.5 秒的蜂鸣声将继续。我做 audioTimer.invalidate(( 来停止计时器从以前的迭代运行。

知道我的代码做错了什么吗?非常感谢

func calculateDistance {
    //here there is code that successfully calculates distance, every 2 seconds
    var timerSeconds = 0.0
    var audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true)
    if globalDistance > 10 { // globalDistance is where i set the distance every 2 seconds, with a timer fired on ViewDidLoad
        timerSeconds = 2
    }
    if globalDistance >= 0 && globalDistance <= 10 {
        timerSeconds = 0.5
    }
    audioTimer.invalidate()
    audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true)
    audioTimer.fire()
}
func audioPlayer(){
    AudioServicesPlaySystemSound(1104)
}

基本思想是确保没有代码路径来启动Timer而不停止任何先前的路径。您当前的代码有几个路径,通过这些路径,现有计时器在启动下一个计时器之前不会失效。

此外,我建议您仅在新的蜂鸣频率与旧的蜂鸣频率不同时才使旧Timer无效。(如果旧的 2 秒计时器可以正常工作,为什么要使 2 秒重复哔哔声无效并启动另一个计时器?

因此,这意味着您将:

  • 函数中提取TimerTimeInterval变量;
  • 仅当它确定蜂鸣音间隔已更改时,才执行"新计时器"过程;并且
  • 确保在创建新计时器之前始终使旧计时器无效。

例如:

private var audioTimer: Timer?
private var beepInterval: TimeInterval?
private func updateBeepIntervalIfNeeded() {
    // here there is code that successfully calculates distance, called with whatever frequency you want
    let newBeepInterval: TimeInterval
    if globalDistance > 10 {
        newBeepInterval = 2
    } else if globalDistance >= 0 {
        newBeepInterval = 0.5
    } else {
        fatalError("less than 0?!")   // I'm inferring from your code that this cannot happen, but by using `let` above, Swift warned me that we had a path of execution we hadn't previously considered
    }
    if beepInterval != newBeepInterval {
        beepInterval = newBeepInterval
        audioTimer?.invalidate()
        audioTimer = Timer.scheduledTimer(timeInterval: beepInterval!, target: self, selector: #selector(beep(_:)), userInfo: nil, repeats: true)
        audioTimer!.fire()
    }
}
@objc func beep(_ timer: Timer) {
    // perform beep here
}

问题

这里有几个问题。

首先,我想强调引用和实例之间的区别。调用 call 初始值设定项时,系统会为新对象分配一段内存,并为您提供对该内存的引用,该内存存储在您将其分配给的任何变量中。您可以将此引用分配给其他变量,这些变量将创建引用的副本。这些变量中的每一个都引用相同的原始对象。此对象将继续存在于内存中,直到没有更多变量引用它。

在您的情况下,您不是直接调用初始值设定项,而是调用一个用于类似目的的静态方法。将代表您分配一个新对象,并为您提供一个引用,然后将其分配给audioTimer。然而,这有一个问题。当您调用 Timer.scheduledTimer(timeInterval:target:selector:userInfo:repeats:) 时,新构造的计时器将为您安排在当前运行循环中。运行循环负责在正确的时间触发计时器。这样做的结果是,现在运行循环正在引用计时器,从而防止计时器对象被销毁。除非您使计时器无效以将其从其运行循环中取消注册,否则计时器将继续存在并永远触发,即使您删除了对它的尊重。

现在让我们看一下您的代码,并对正在发生的事情进行一些解释:

func calculateDistance {
    //here there is code that successfully calculates distance, every 2 seconds
    var timerSeconds = 0.0
    // 1) Initialize timer #1
    var audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true)
    if globalDistance > 10 { // globalDistance is where i set the distance every 2 seconds, with a timer fired on ViewDidLoad
        timerSeconds = 2
    }
    if globalDistance >= 0 && globalDistance <= 10 {
        timerSeconds = 0.5
    }
    // 2) Invalidate timer #1 (timer #1 is useless)
    audioTimer.invalidate()
    // 3) Initialize timer #1
    audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true)
    // 4) Fire timer #2 immediately
    audioTimer.fire()

} // At the end of this method body:
// - Timer #2 was never invalidated
// - audioTimer no longer references Timer #2, but:
// - Timer #2's runloop still references it, keeping it alive
// - Timer #2 is leaked
// ... and will continue firing forever.
func audioPlayer(){
    AudioServicesPlaySystemSound(1104)
}

我们可以看到,在第一节中发出了Timer,它应该在 timerSeconds 秒内触发,0。在第 2 节中,该计时器失效。尽管Timer将在 0 秒内触发,但几乎可以肯定它的运行循环还没有机会触发它。因此,这个时间是创建的,永远不会触发,然后失效。它根本没有理由在那里存在。

然后,在第 3 节中,创建并计划计时器 #2。 它是在第 4 节手动触发的,然后永久泄漏。

解决方案

您需要一个保存对计时器的引用的实例变量。否则,您无法使已安排的计时器失效。

其次,您需要在适当的时间使计时器失效。

我建议你看看Rob的答案作为一个例子。

你正在创建一个新的,无限重复的计时器一次,立即使其失效(为什么?(,然后创建另一个(为什么?(,它永远泄漏。

您正在创建一个新的计时器,使计时器失效,然后再次创建一个计时器。

您可以尝试创建计时器,并在调用 audioPlayer 函数时,根据 timerSeconds 变量的值检查要播放的声音。

最新更新